NeuroAgent

Understanding Access-Control-Allow-Origin in CORS

Learn how Access-Control-Allow-Origin header enables cross-origin requests in CORS. Discover proper implementation steps, preflight requests, and security best practices for enabling JavaScript from one domain to access resources on another.

Question

How does the ‘Access-Control-Allow-Origin’ header work in cross-origin resource sharing (CORS)?

I’ve been trying to understand how to properly implement CORS headers to allow JavaScript code from one domain to access resources on another domain. My current understanding is:

  1. A client downloads JavaScript code MyCode.js from http://siteA (the origin).
  2. The response header of MyCode.js contains Access-Control-Allow-Origin: http://siteB, which I thought meant that MyCode.js was allowed to make cross-origin references to site B.
  3. The client triggers functionality in MyCode.js, which makes requests to http://siteB.

However, this approach doesn’t work as expected. I’ve read about cross-origin resource sharing on Wikipedia and the W3C recommendation, but I’m still unclear on how to properly use this header.

I have control over both site A and site B. How do I enable JavaScript code downloaded from site A to access resources on site B using the Access-Control-Allow-Origin header?

Note: I prefer not to use JSONP as a solution.

NeuroAgent

The Access-Control-Allow-Origin header works by indicating which origins are permitted to access a resource from a different domain. When a browser makes a cross-origin request, the server responds with this header to specify which domains can receive the response. The header must be set by the server hosting the resource (siteB), not by the server providing the JavaScript code (siteA). This means siteB needs to include Access-Control-Allow-Origin: http://siteA in its responses to allow siteA’s JavaScript to access its resources.

Contents


Understanding the Access-Control-Allow-Origin Header

The Access-Control-Allow-Origin header is an HTTP response header that tells browsers whether a resource can be shared with the requesting code from different origins. When you control both siteA and siteB, you need to configure siteB to include this header in its responses.

The header can have three possible values:

  • Access-Control-Allow-Origin: * - Allows any origin to access the resource
  • Access-Control-Allow-Origin: http://siteA - Only allows requests from a specific origin
  • Access-Control-Allow-Origin: null - Used for opaque responses

A critical security restriction is that when credentials are involved, the server cannot use the wildcard (*) and must specify an explicit origin source.

Key Point: The Access-Control-Allow-Origin header is set by the server that hosts the resource being requested (siteB), not by the server that provides the JavaScript code (siteA). This is why your current approach doesn’t work.

How CORS Actually Works

Cross-Origin Resource Sharing (CORS) is a mechanism that allows browsers to make requests to different origins than the one that served the web page. Here’s the correct flow:

  1. SiteA serves JavaScript code to the client
  2. The JavaScript code makes a request to siteB
  3. SiteB’s server responds with the appropriate CORS headers
  4. The browser checks if the origin is allowed
  5. If allowed, the browser makes the response available to the JavaScript code

According to Wikipedia, “When performing certain types of cross-domain Ajax requests, modern browsers that support CORS will initiate an extra ‘preflight’ request to determine whether they have permission to perform the action.”

The browser automatically handles the CORS security checks. If siteB doesn’t include the proper Access-Control-Allow-Origin header, the browser will block the response from being accessed by the JavaScript code, even though the HTTP request succeeded.

Preflight Requests and the OPTIONS Method

For certain types of requests, browsers send a preflight request using the OPTIONS method before making the actual request. This happens when:

  • The request uses methods other than GET, HEAD, or POST
  • The request uses POST with Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain
  • The request includes custom headers

A preflight request includes headers like:

  • Origin: The requesting origin
  • Access-Control-Request-Method: The HTTP method to be used
  • Access-Control-Request-Headers: (Optional) Custom headers to be sent

The server must respond to preflight requests with headers indicating what’s allowed:

Access-Control-Allow-Origin: http://siteA
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

As explained by PortSwigger Web Security Academy, “When a CORS request is received, the supplied origin is compared to the whitelist. If the origin appears on the whitelist then it is reflected in the Access-Control-Allow-Origin header so that access is granted.”

Proper Implementation Steps

To enable JavaScript from siteA to access resources on siteB:

1. Configure siteB’s Server Responses

SiteB must include the appropriate CORS headers in its HTTP responses:

http
Access-Control-Allow-Origin: http://siteA
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

2. Handle Preflight Requests

SiteB must respond to OPTIONS requests with the appropriate headers:

http
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://siteA
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

3. Special Cases for Credentials

If you need to send cookies or authentication headers, you must:

  • Include Access-Control-Allow-Credentials: true
  • NOT use the wildcard (*) for Access-Control-Allow-Origin
  • Specify the exact origin

Example:

http
Access-Control-Allow-Origin: http://siteA
Access-Control-Allow-Credentials: true

4. Server-Side Implementation Examples

Here are implementations for different server types:

Node.js Express

javascript
const express = require('express');
const app = express();

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://siteA');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});

app.options('*', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'http://siteA');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.send();
});

Nginx Configuration

nginx
server {
    listen 80;
    server_name siteB.com;
    
    location / {
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' 'http://siteA';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE';
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }
        
        add_header 'Access-Control-Allow-Origin' 'http://siteA' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
        
        # Your existing configuration
    }
}

Common Mistakes and Solutions

Mistake 1: Setting the Header on the Wrong Server

Problem: Setting Access-Control-Allow-Origin on siteA instead of siteB
Solution: The header must be in responses from siteB, the server being requested

Mistake 2: Using Wildcard with Credentials

Problem: Using Access-Control-Allow-Origin: * when sending cookies or auth headers
Solution: Use specific origins when credentials are involved

Mistake 3: Missing Preflight Handling

Problem: Not responding to OPTIONS requests
Solution: Implement OPTIONS handler on siteB

Mistake 4: Requesting from File://

Problem: CORS doesn’t work when opening HTML files directly from disk
Solution: Use a local server during development

Security Considerations

When implementing CORS, consider these security best practices:

  1. Principle of Least Privilege: Only allow specific origins that need access
  2. Avoid Wildcard for Sensitive Data: Never use * for APIs that handle sensitive information
  3. Validate Origins: Ensure the origin in requests matches what you expect
  4. Consider Time Limits: Set appropriate Access-Control-Max-Age values for preflight caching
  5. Monitor for Abuse: Keep logs of CORS requests to detect potential security issues

According to PortSwigger, “the cross-domain server can permit reading of the response when credentials are passed to it by setting the CORS Access-Control-Allow-Credentials header to true.”


Sources

  1. Cross-Origin Resource Sharing (CORS) - HTTP | MDN
  2. Access-Control-Allow-Origin header - HTTP | MDN
  3. Cross-origin resource sharing - Wikipedia
  4. CORS and the Access-Control-Allow-Origin response header | Web Security Academy
  5. Enable Cross-Origin Requests (CORS) in ASP.NET Core | Microsoft Learn
  6. CORS, Preflight request and OPTIONS Method - DEV Community
  7. Understanding CORS and Preflight Requests in APIs | Medium

Conclusion

To properly enable JavaScript from siteA to access resources on siteB using CORS:

  1. Configure siteB to include Access-Control-Allow-Origin: http://siteA in its responses
  2. Handle preflight requests with proper OPTIONS method responses
  3. Avoid using wildcard (*) when credentials are involved
  4. Test thoroughly with different request types and headers
  5. Follow security best practices to maintain proper access controls

Your initial approach was incorrect because you were setting the CORS header on siteA instead of siteB. Remember that the server being requested (siteB) is responsible for indicating which origins can access its resources. By implementing the correct headers on siteB, you’ll enable cross-origin requests while maintaining security through the browser’s CORS enforcement mechanism.