Cross-Origin Resource Sharing (CORS) Explained

Cross-Origin Resource Sharing (CORS) is a security feature implemented in web browsers to control how web pages in one domain can make requests to resources in a different domain. It's a part of the broader security policy called the Same-Origin Policy (SOP), which is designed to safeguard users from malicious web content.

Before diving deep into the details of CORS, lets understand what does "origin" means in the context of Web Security.

What is origin?

In the context of Web Security and CORS (Cross-Origin Resource Sharing), the term "origin" is a fundamental concept used to determine whether a particular web page is allowed to access resources from a server on a different domain. An origin is defined by the combination of three elements: the scheme (protocol), the host (domain), and the port.

  • Scheme (Protocol): This is the method used to access a resource on the Internet, such as HTTP or HTTPS.

  • Host name (Domain): This is the domain name of the website, like example.com.

  • Port: This is the network port used to access the resource. Common ports include 80 for HTTP and 443 for HTTPS. If a port isn't specified in the URL, it defaults to these standard ports for the respective protocols.

Same Origin vs Different Origin

Two URLs have the same origin only if all three of these components (scheme, host, port) are identical. Otherwise, they are considered different origins.

Examples of Same Origin:

  1. http://example.com/page1 and http://example.com/page2

    • Same protocol (http), same domain (example.com), and implicitly the same port (80, the default for http).
  2. https://example.com:443/ and https://example.com/contact

    • Same protocol (https), same domain (example.com), and the same port (443, the default for https).

Examples of Different Origin

  1. http://example.com and http://www.example.com

  2. https://example.com and http://example.com

    • Different schemes (https vs http).
  3. http://example.com and http://example.com:8080

    • Different ports (default 80 vs 8080).
  4. http://example.com and http://example.org

What is Same-Origin Policy (SOP)?

The Same-Origin Policy (SOP) is a critical security concept in web application development. It's a policy enforced by web browsers to prevent potentially malicious scripts on one origin from interacting with resources from another origin. This policy is foundational to web security, as it helps to protect users' data from various types of web-based attacks.

Key Elements of SOP:

  1. Definition of Origin: As mentioned earlier, an origin is defined by the combination of the scheme (protocol, e.g., HTTP or HTTPS), the host (domain), and the port. SOP considers URLs with the same scheme, host, and port as having the same origin therefore allowing client browser to make these kind of requests.

  2. Restrictions Imposed by SOP:

  3. Exceptions and Workarounds:

    • Cross-Origin Resource Sharing (CORS): As an exception to SOP, CORS allows servers to specify who can access their resources and under what conditions. It uses HTTP headers to tell the browser to allow web applications from one origin to access resources from another origin.

    • JSONP (JSON with Padding): Before CORS, JSONP was a common method to bypass SOP restrictions. It involves loading a script from a different origin by dynamically adding a <script> tag to the DOM.

    • postMessage() API method: This API method allows secure cross-origin communication between Window objects, such as iframes, pop-ups, or tabs.

  4. Security Implications: SOP is essential for preventing security issues like Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF). By restricting how scripts can interact with resources from other origins, it adds a layer of protection for user data and privacy.

  5. Evolution and Support: Over the years, SOP has evolved to adapt to the increasing complexity of web applications. Modern browsers are equipped with robust mechanisms to enforce SOP while providing ways (like CORS) to safely relax these restrictions when necessary.

The Same-Origin Policy is a foundational security mechanism in web browsers that restricts web pages from interacting with resources from different origins. Its primary goal is to protect user data and prevent malicious scripts from compromising web interactions.

What is Cross-Origin Resource Sharing (CORS)?

Cross-origin resource sharing (CORS) is an extension of the same-origin policy (SOP) that enables controlled access to resources located outside of a given domain. A web page or a web app on a given domain may freely embed cross-origin images, stylesheets, scripts, iframes, and videos.

CORS provides potential for cross-domain attacks, if a website's CORS policy is poorly configured and implemented. CORS is not a protection against cross-origin attacks such as cross-site request forgery (CSRF). Trusting all origins (*) is an example of an insecure CORS configuration. Learn more >>

Here's a breakdown of the key aspects of CORS:

The Need for CORS

While SOP is crucial for security, there are legitimate reasons for a web page to request resources from a different domain. For example, modern web applications often need to interact with resources from different domains, such as:

  • Fetching data from third-party APIs

  • Loading images or fonts from external sources

  • Embedding content from other websites (e.g., videos, social media posts)

How CORS Works

CORS allows servers to specify who can access their assets and under what conditions. When a web browser makes a cross-origin request (such as an HTTP request to load an image, make an AJAX call, etc.), it sends an HTTP request to the server hosting the resource. This request includes an Origin header that indicates the domain of the requesting page.

CORS is a security feature, not a vulnerability. It allows web developers to relax the SOP under controlled conditions. However, configuring CORS incorrectly can expose a website to risks like data theft or malicious attacks.

Image credits: MDN

Preflight Request (for non-simple requests):

💡
Some requests that don't trigger a CORS preflight are called simple requests. These requests are made with no preliminary communication with the server.

  • Before making a potentially sensitive request (e.g., using methods like PUT, DELETE, or custom headers), the browser sends a preflight request (using the OPTIONS method) to the server.

  • The server responds with CORS headers indicating whether the cross-origin request is allowed and any restrictions. For example, a client browser might be asking a server if it would allow a DELETE request, before sending a DELETE request, by using a preflight request:

      OPTIONS /resource/foo
      Access-Control-Request-Method: DELETE
      Access-Control-Request-Headers: origin, x-requested-with
      Origin: https://foo.bar.org
    

    If the server allows it, then it will respond to the preflight request with an Access-Control-Allow-Methods response header, which lists DELETE:

      HTTP/1.1 204 No Content
      Connection: keep-alive
      Access-Control-Allow-Origin: https://foo.bar.org
      Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
      Access-Control-Max-Age: 86400
    

    Actual Request:

  • If the preflight request is successful, the browser sends the actual cross-origin request with the necessary CORS headers (Step 4).

  • The server checks the CORS headers and either allows or blocks the request (Step 5).

    Browser's Role:
    The browser enforces CORS. After receiving the server's response, the browser checks the CORS headers (Step 3). If the server's response indicates that the request is allowed, the browser proceeds with the request. If not, the browser blocks the request, and the web page cannot access the resource.

Key CORS Headers

The server checks the Origin header against its CORS policy. If the request is allowed, the server responds with the appropriate CORS headers, such as Access-Control-Allow-Origin. This header tells the browser whether the requesting domain is permitted to access the resource.

The key CORS Headers:

Workshop 1: CORS Essentials with Python Flask

💡
Code files for the workshops available at: https://github.com/Brain2life/blog-flask-cors

In this workshop, we'll use Python for the server side and simple HTML and JavaScript for the client side. This workshop assumes basic knowledge of Python, HTML, and JavaScript. All testing is done locally.

Step 1: Setting Up the Environment

Ensure Python3 is installed on your system. You can download it from python.org.

Step 2: Creating the Server

  1. Install Flask: Flask is a lightweight web framework for Python. Install it using pip:

     pip3 install Flask Flask-CORS
    
    💡
    Flask-CORS - A Flask extension for handling Cross Origin Resource Sharing (CORS), making cross-origin AJAX possible.
  2. Create a Python File for the Server: Name it server.py.

    Write the following code in server.py:

     from flask import Flask
     from flask_cors import CORS
    
     app = Flask(__name__)
     CORS(app)  # This will enable CORS for all routes
    
     @app.route('/')
     def home():
         return "Hello, CORS-enabled world!"
    
     if __name__ == "__main__":
         app.run(debug=True)
    

    This code creates a basic Flask server with CORS enabled for all routes.

Step 3: Running the Server

Step 4: Creating the Client Side Application

  1. Create an HTML File: Name it index.html.

  2. Client Code: Write the following HTML and JavaScript in index.html:

     <!DOCTYPE html>
     <html>
     <head>
         <title>CORS Test</title>
         <script>
             function makeRequest() {
                 fetch('http://127.0.0.1:5000/')
                     .then(response => response.text())
                     .then(data => alert(data))
                     .catch(error => alert('Error: ' + error));
             }
         </script>
     </head>
     <body>
         <h1>CORS Test</h1>
         <button onclick="makeRequest()">Make Request</button>
     </body>
     </html>
    

Step 5: Testing CORS

  1. Open the HTML File in a Browser: This is your client application.

  2. Click the 'Make Request' Button: This will trigger a JavaScript fetch request to your Flask server.

What to Expect

  • If CORS is set up correctly, you should see an alert with the message "Hello, CORS-enabled world!".

  • If you disable CORS in your Flask server by commenting out CORS(app) and reload the server, the browser will block the request, and you should see an error in the alert.

Workshop 2: Allow Specific Origins

You can specify which origins are allowed to access your server. This is useful if you want to restrict access to known and trusted websites.

Modify server.py to include:

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
cors = CORS(app, resources={r"/*": {"origins": "http://127.0.0.1:8000"}})  # Only allow requests from "http://127.0.0.1:8000/"

@app.route('/', methods=['GET'])
def home():
    return "Hello, CORS-configured world!"

if __name__ == "__main__":
    app.run(debug=True)

This configuration will only allow CORS requests from the origin http://localhost:8000.

To send a request from http://localhost:8000 to your Flask server configured for CORS, you need to set up a simple web server on localhost running on port 8000 and create a client-side application (like an HTML page with JavaScript) that makes the request. Here's how you can do it:

Step 1: Set Up a Simple Web Server

You can use Python's built-in HTTP server for this.

  1. Create a Directory: Make a new directory for your web server, let's call it web_client and navigate to it.

  2. Start the Server: Run the following command to start a simple HTTP server on port 8000:

    • For Python 3.x:

        python -m http.server 8000
      

Step 2: Create a Client-side Application

  1. Create an HTML File: Inside the web_client directory, create an HTML file named index.html.

  2. Add HTML and JavaScript: Edit index.html to include the following content:

     <!DOCTYPE html>
     <html>
     <head>
         <title>CORS Test Client</title>
         <script>
             function makeCORSRequest() {
                 fetch('http://127.0.0.1:5000/')  // URL of your Flask server
                     .then(response => response.text())
                     .then(data => alert(data))
                     .catch(error => alert('CORS Error: ' + error));
             }
         </script>
     </head>
     <body>
         <h1>CORS Request Test</h1>
         <button onclick="makeCORSRequest()">Make CORS Request</button>
     </body>
     </html>
    

    This HTML file contains a button that, when clicked, makes a fetch request to your Flask server.

Step 3: Test the CORS Configuration

  1. Open Your Web Browser: Navigate to http://localhost:8000. You should see your HTML page with the "Make CORS Request" button.

  2. Click the Button: This action will attempt to make a request to your Flask server at http://127.0.0.1:5000/.

  3. Observe the Behavior:

    • If CORS is configured correctly on your Flask server to accept requests from http://localhost:8000, the fetch should succeed, and you'll see the server's response.

    • If the CORS configuration does not allow requests from this origin, the browser will block the request, and you'll see a CORS error.

This test will demonstrate how CORS works in a real-world scenario, showing how the browser restricts or allows cross-origin HTTP requests based on the server's CORS configuration.

References:

  1. What is CORS?

  2. Cross-Origin Resource Sharing (CORS)

  3. Same-origin policy

  4. Same-origin policy (SOP)

  5. Using the Fetch API

  6. Cross-site request forgery (CSRF)

  7. Lab: CORS vulnerability with basic origin reflection