Cool Boiled WaterCool Boiled Water Logo
HomeBlog
Browser meme

In-Depth Understanding of Browser Cross-Origin Principles and Multiple Solutions to Cross-Origin Issues

Chrome
Devops
2024 Dec 022603 words|Estimated reading time: 14 minutes

Cross-origin issues are a very common problem in front-end and back-end separated development. This article will provide a detailed explanation of the principles behind browser cross-origin behavior and summarize multiple solutions to resolve cross-origin issues.

What is Cross-Origin

Cross-origin refers to the scenario where JavaScript scripts running in the browser make requests to servers of different origins, but these requests are restricted by the browser's Same-Origin Policy.

The Same-Origin Policy is an important security mechanism that limits how scripts loaded from one origin can interact with resources from other origins. This is to prevent malicious websites from stealing sensitive data without the user's knowledge.

In other words, cross-origin issues occur only when client-side requests are made from a browser to a server. These issues do not exist in other scenarios because cross-origin restrictions are enforced by the browser.

Definition of "Same-Origin"

Two URLs are considered same-origin if they meet all of the following conditions:

  • Protocol is the same (e.g., http:// and https:// are considered different origins).
  • Domain name is the same (e.g., example.com and api.example.com are considered different origins).
  • Port number is the same (e.g., http://example.com:80 and http://example.com:8080 are considered different origins).

If any one of these aspects is different, the browser will classify it as a cross-origin request.

Why Does the Same-Origin Policy Exist

The Same-Origin Policy was introduced to protect users' privacy and data security by preventing malicious websites from accessing sensitive information without authorization. Browsers, as the core tool for client-server interactions, have access to sensitive user data like cookies and session information. Without Same-Origin restrictions, malicious websites (e.g., phishing sites) could easily exploit these data to launch attacks.

If the Same-Origin Policy were absent, security vulnerabilities such as Cross-Site Request Forgery (CSRF) would become far more severe and challenging to prevent.

What is CSRF (Cross-Site Request Forgery)

CSRF is an attack where a malicious website exploits the user's authenticated session on a target website to perform unauthorized actions, such as transferring funds or modifying account information.

For example, suppose a user is logged into a banking website like https://bank.com, and the browser has stored a valid login cookie. If the user unknowingly visits a malicious website like http://malicious.com, the malicious site can make requests like the following:

<img src="https://bank.com/transfer?to=attacker&amount=5000">

Since the browser automatically includes the user's login cookie in the request, the banking server may mistakenly treat the request as legitimate and complete the transfer.

How Does the Same-Origin Policy Mitigate These Risks

  1. Blocking Cross-Origin DOM Access
    The Same-Origin Policy prevents JavaScript from directly accessing content on pages from a different origin, ensuring that malicious scripts cannot steal sensitive data.

  2. Restricting Access to Cookies and LocalStorage
    Browsers only allow scripts from the same origin to access cookies, LocalStorage, and SessionStorage. This prevents unauthorized sites from stealing user data.

  3. Restricting Cross-Origin XMLHttpRequest and Fetch Calls
    The Same-Origin Policy blocks front-end cross-origin requests unless the target server explicitly allows them through mechanisms like CORS (Cross-Origin Resource Sharing).

  4. Preventing Cross-Origin iframe Script Interaction
    The Same-Origin Policy prohibits scripts in cross-origin iframes from interacting with each other, ensuring that pages from different origins cannot directly share data.**

Common Scenarios of Cross-Origin Issues

  1. Frontend-Backend Separation
    In a frontend-backend separated architecture, the frontend might run on https://frontend.example.com, while the backend service operates on https://api.example.com. When the frontend sends AJAX or Fetch requests to the backend, cross-origin restrictions are triggered.

  2. Cross-Origin with Different Ports
    During local development, the frontend might run on http://localhost:3000, while the backend serves requests on http://localhost:5000. Since the ports differ, the browser treats them as different origins, leading to cross-origin issues.

  3. CDN or Third-Party Services
    When the frontend loads resources (e.g., fonts, JavaScript files) from an external CDN, or interacts with third-party services, the browser may block the request if the service is not properly configured to handle cross-origin responses.

Common Solutions to Cross-Origin Issues

To address cross-origin issues, solutions can be implemented at the frontend, backend, or through a proxy layer.

[Frontend] JSONP (Supports Only GET Requests)

JSONP (JSON with Padding) is a technique used to solve browser cross-origin issues. It leverages the <script> tag’s ability to load external scripts without being subject to cross-origin restrictions. Below is a detailed explanation of its principles, implementation steps, advantages, disadvantages, and use cases.

Principle

The core idea of JSONP is dynamically creating a <script> tag to load remote scripts. Since browsers do not enforce cross-origin restrictions on <script> tags, this allows fetching data from different origins.

In a JSONP request, the frontend specifies a callback function name when requesting data from the server. The server responds by wrapping the data in JavaScript code that calls the callback function, enabling data transfer to the client.

Implementation Steps

  1. Client Prepares the Request:
    The frontend dynamically creates a <script> tag to send a request to the server. The request includes the name of the callback function.

    function handleResponse(data) {
        console.log(data);
    }
    
    const script = document.createElement('script');
    script.src = `https://api.example.com/data?callback=handleResponse`;
    document.body.appendChild(script);
  2. Server Processes the Request:
    The server extracts the callback function name from the request and wraps the data in JavaScript code to invoke the callback.

    // Data generated on the server
    const data = { message: "Hello, World!" };
    
    // Construct the response
    const response = `handleResponse(${JSON.stringify(data)})`;
    
    // The server sends this response back to the client
    return response;
  3. Browser Executes the Callback:
    The browser executes the returned JavaScript code, invoking the handleResponse function and passing the data to it.

    // Final executed result
    handleResponse({ message: "Hello, World!" });

Advantages and Disadvantages

  • Advantages:

    • Simple and Easy to Use: JSONP requires minimal setup; you just need to specify the callback function name in the request.
    • Good Compatibility: It works with older browsers since it relies on <script> tags instead of modern APIs like XMLHttpRequest.
  • Disadvantages:

    • GET Requests Only: JSONP supports only GET requests and cannot handle POST or other HTTP methods.
    • Security Risks: JSONP executes JavaScript code directly, which may lead to XSS (Cross-Site Scripting) attacks if the server returns untrusted code. This poses a risk of sensitive data being stolen.
    • Debugging Challenges: Since the response is JavaScript code, debugging JSONP requests is less convenient compared to standard JSON responses.

Use Cases

  • Public APIs: Many public APIs support JSONP, enabling developers to easily fetch data from external services.
  • Third-Party Services: JSONP is a quick and straightforward solution for fetching data from third-party services, particularly when the backend does not support CORS.

[Backend] CORS and Preflight Requests

CORS (Cross-Origin Resource Sharing) is a mechanism that allows browsers to make requests to servers with different origins. To ensure security, when a browser sends a complex cross-origin request, it first sends a preflight request.

Here are some key points to note:

  • Do all cross-origin requests trigger preflight requests?
    No, only non-simple requests trigger preflight requests. Simple requests do not.

  • If there’s a preflight request, is it always a cross-origin request?
    Yes, preflight requests are part of the CORS mechanism and occur only for cross-origin requests.

What is a Preflight Request?

A preflight request is an OPTIONS request that the browser sends before making the actual cross-origin request. Its purpose is to ask the target server whether the actual request is allowed. This mechanism mainly handles complex requests, such as those using HTTP methods like PUT or DELETE or those with custom headers.

  • Simple requests do not trigger preflight requests.
  • Non-simple requests require a preflight request.

What is a Simple Request?

A cross-origin request is considered a "simple request" if it meets the following conditions:

  1. Request Method: The request uses one of these methods:

    • GET
    • POST
    • HEAD
  2. Request Headers: Only the following headers are included (these are automatically set by the browser for "safe" CORS requests):

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (limited to application/x-www-form-urlencoded, multipart/form-data, or text/plain)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  3. No Custom Headers: The request does not include any developer-defined custom headers.

  4. No Special Mechanisms: The resource does not use complex mechanisms, such as HTTP authentication or custom behavior.

If a cross-origin request meets all these conditions, it is classified as a simple request, and no preflight request is triggered.

What is a Non-Simple Request?

If a request does not meet any of the criteria for a simple request—for example, it uses the PUT method or includes a custom header like Authorization—then the browser sends a preflight request (an OPTIONS request) to verify whether the actual request is permitted.

Preflight Request Workflow

  1. Browser Sends a Preflight Request:
    For complex cross-origin requests, the browser automatically sends an OPTIONS request to the server before the actual request. For example:

    OPTIONS /api/resource HTTP/1.1
    Host: api.example.com
    Origin: https://frontend.example.com
    Access-Control-Request-Method: POST
    Access-Control-Request-Headers: Content-Type
    • Origin: Specifies the origin of the request.
    • Access-Control-Request-Method: Indicates the HTTP method of the actual request.
    • Access-Control-Request-Headers: Lists custom headers included in the actual request.
  2. Server Responds to the Preflight Request:
    The server responds with the permitted origins, methods, and headers. For example:

    HTTP/1.1 204 No Content
    Access-Control-Allow-Origin: https://frontend.example.com
    Access-Control-Allow-Methods: GET, POST, PUT, DELETE
    Access-Control-Allow-Headers: Content-Type
  3. Browser Sends the Actual Request:
    If the preflight response confirms that the request is allowed, the browser proceeds to send the actual request.

Backend Configuration for CORS

To support CORS, the backend must include appropriate response headers, especially when handling OPTIONS requests. Below is an example of how to configure CORS in a backend using Node.js:

app.options('/api/resource', (req, res) => {
    res.setHeader('Access-Control-Allow-Origin', 'https://frontend.example.com');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.sendStatus(204); // No Content
});

Why Distinguish Between Simple and Complex Requests?

To understand the difference between simple and complex requests, it’s essential to recognize the original design intent of HTTP GET requests.

  • GET requests are designed to be "safe": They are intended to retrieve resources without modifying server state. This makes them less risky, so browsers allow cross-origin GET requests (i.e., simple requests) without triggering preflight checks.

  • Compatibility with legacy systems: Before CORS existed, cross-origin requests were already common, especially for loading static resources like images, scripts, and stylesheets. CORS was designed to accommodate these use cases without breaking existing systems.

The CORS mechanism assumes that simple requests are inherently safe, while complex requests require additional verification to ensure security.

Historical Reasons for Supporting Simple Requests:

  1. Cross-origin GET requests for resources (e.g., images, scripts) were already widely used before CORS.
  2. The browser’s Same-Origin Policy originally restricted only JavaScript access to cross-origin responses, not the requests themselves.
  3. CORS was designed to balance security with real-world needs, allowing simple requests to function without additional restrictions.

This distinction allows legitimate cross-origin scenarios (e.g., loading third-party libraries, CDN resources, or APIs) to work seamlessly while ensuring security for more sensitive operations.

Advantages and Disadvantages of CORS

  • Advantages:

    • Supports Complex Requests: CORS enables the use of various HTTP methods (PUT, DELETE, etc.) and custom headers.
    • Enhanced Security: The preflight mechanism ensures that only authorized requests are processed.
  • Disadvantages:

    • Backend Dependency: CORS requires backend support, which can add complexity to the server configuration.
    • Increased Network Overhead: Preflight requests add extra network latency, which can impact performance in scenarios with frequent requests.

[Frontend] Frontend Proxy

In a development environment, frontend development tools (like Webpack and Vite) can be configured to use a proxy, forwarding cross-origin requests to the backend. For example:

// Vite Configuration
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:5000', // Forwards to the backend service
        changeOrigin: true,
      },
    },
  },
};

In simple terms, when the frontend web page at localhost:3000/index.html makes an AJAX call, it can use the endpoint localhost:3000/api to forward the request. Vite will automatically inject a route, forwarding all requests for that route directly to the local service at http://localhost:5000.

Pros and Cons

  • Pros: Easy to use in a development environment.
  • Cons: Only suitable for local development; a different solution is needed for production.

[Frontend and Backend] Nginx Reverse Proxy

Using Nginx for reverse proxying can effectively address cross-origin issues and is suitable for production environments.

Two Ways Nginx Solves Cross-Origin Issues

Method 1: Avoiding Cross-Origin Issues with Reverse Proxy
By proxying backend APIs to the same domain as the frontend, the browser treats them as same-origin, thus preventing cross-origin restrictions.

Assuming the frontend runs at https://frontend.example.com and the backend runs at http://backend.example.com, you can install Nginx on the server bound to the frontend domain, with the following configuration:

server {
    listen 80;
    server_name frontend.example.com;

    # Frontend Static Resources
    location / {
        root /path/to/frontend/build;
        index index.html;
        try_files $uri /index.html;  # Supports SPA routing
    }

    # Backend API Proxy
    location /api/ {
        proxy_pass http://backend.example.com;  # Forwards to the backend service
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Request Flow

When a user accesses https://frontend.example.com/api, Nginx forwards the request to http://backend.example.com. The browser sees this as a same-origin request and loads the data normally.

Advantages

  • Avoids cross-origin issues without user awareness.
  • Simple and efficient, suitable for most scenarios.

Method 2: Adding CORS Response Headers
If the frontend and backend are on different domains, you can use Nginx to add CORS headers in the backend service responses.

Assuming the frontend runs at https://frontend.example.com and the backend at https://api.example.com, you can set up Nginx on the backend server with the following configuration:

server {
    listen 80;
    server_name api.example.com;

    # Backend Service Proxy
    location / {
        proxy_pass http://backend_service;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # Add CORS Response Headers
        add_header 'Access-Control-Allow-Origin' 'https://frontend.example.com';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
        add_header 'Access-Control-Allow-Credentials' 'true';

        # Handle Preflight Requests
        if ($request_method = 'OPTIONS') {
            return 204;
        }
    }
}

Request Flow

When a user accesses https://frontend.example.com, the frontend makes a cross-origin request to https://api.example.com. Nginx responds with CORS headers, allowing the browser to permit cross-origin access.

Advantages

  • Flexibly supports complex cross-origin scenarios (like multiple frontend domains).
  • No need to modify backend code; Nginx acts as an intermediary, forwarding the HTTP response from the backend.

Summary

The Essence of Cross-Origin Issues

Cross-origin problems arise from the security restrictions of browsers, specifically the same-origin policy; fundamentally, there are no cross-origin issues between servers.

Comparison of Common Solutions

MethodAdvantagesDisadvantages
JSONPSimple to use, good compatibilitySupports only GET requests, limited functionality
CORSSupports complex requests, standardized solutionRequires backend support, slightly complex configuration
Frontend ProxyConvenient for local development, quickly resolves cross-origin issuesOnly suitable for development environments
Nginx Reverse ProxyAvoids cross-origin issues without user awareness, suitable for productionLower complexity but requires Nginx deployment
Nginx Adding CORS HeadersSupports complex cross-origin scenarios, high flexibilityConfiguration can be complex, suitable for production environments

Recommended Nginx Methods

  • Reverse Proxy: Ideal for scenarios where the frontend and backend are separate but need to avoid cross-origin issues.
  • Adding CORS Headers: Suitable for fully decoupled frontend and backend architectures that require explicit control over cross-origin access.

By properly configuring Nginx, you can easily resolve cross-origin issues while ensuring both flexibility and security for your system.

Content

What is Cross-Origin Definition of "Same-Origin" Why Does the Same-Origin Policy Exist What is CSRF (Cross-Site Request Forgery) How Does the Same-Origin Policy Mitigate These Risks Common Scenarios of Cross-Origin Issues Common Solutions to Cross-Origin Issues [Frontend] JSONP (Supports Only GET Requests) [Backend] CORS and Preflight Requests [Frontend] Frontend Proxy [Frontend and Backend] Nginx Reverse Proxy Summary The Essence of Cross-Origin Issues Comparison of Common Solutions Recommended Nginx Methods
Switch To PCThank you for visiting, but please switch to a PC for the best experience.