CORS & Same-Origin Policy
The same-origin policy, cross-origin requests, simple vs preflight requests, CORS headers (Access-Control-Allow-Origin, -Methods, -Credentials), and common CORS misconfigurations.
The same-origin policy, cross-origin requests, simple vs preflight requests, CORS headers (Access-Control-Allow-Origin, -Methods, -Credentials), and common CORS misconfigurations.
The Same-Origin Policy (SOP) is the browser's most fundamental security boundary. It prevents a page loaded from one origin from reading the response of a request sent to a different origin. Cross-Origin Resource Sharing (CORS) provides a controlled way to relax this restriction, but getting it wrong is one of the most common API security vulnerabilities on the web.
The Same-Origin Policy (SOP) dictates that a web browser may only allow a script from origin A to read the response of a request sent to origin A. Two URLs share the same origin only if they have the same scheme (http/https), host, and port. For example, https://api.example.com and https://www.example.com are different origins because their hosts differ.
SOP does not block the sending of cross-origin requests - it blocks the reading of the response. A page on https://evil.com can issue a POST request to https://bank.com/api/transfer, but it cannot read the response to extract an anti-CSRF token or confirmation message. This is what prevents most cross-site request forgery (CSRF) data exfiltration.
Cross-Origin Resource Sharing (CORS) is a mechanism that allows servers to selectively relax the SOP. The server includes specific HTTP headers in its response that tell the browser which origins are allowed to read the response. The core header is Access-Control-Allow-Origin (ACAO), which can be set to a specific origin, a wildcard (*), or null.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Request-IdFor simple requests - GET, POST, or HEAD with only standard headers (Accept, Accept-Language, Content-Language, Content-Type with values application/x-www-form-urlencoded, multipart/form-data, or text/plain) - the browser sends the cross-origin request directly and checks the response headers. For non-simple requests (PUT, DELETE, custom headers like Authorization, or Content-Type application/json), the browser first sends a preflight OPTIONS request to check if the server permits the actual request.
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://myapp.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Authorization
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: Authorization
Access-Control-Max-Age: 86400The Access-Control-Allow-Credentials header controls whether cookies and authorisation headers may be included in cross-origin requests. When set to true, the ACAO header must specify a concrete origin - the wildcard * is not allowed. The Vary: Origin header should be included on CORS responses to ensure CDNs cache the correct variant per requesting origin.
Use the simulator below to explore how different CORS configurations affect cross-origin requests. Configure the request method, headers, and server settings to see preflight requests and browser CORS errors in action.
x-custom-token trigger an OPTIONS preflight.Access-Control-Allow-Origin header must match the requesting origin exactly when credentials: include is used.Origin header allows any site to read the response - never do this in production.null as ACAO is dangerous: sandboxed iframes and data: URLs use null as their origin.Every modern web application loads resources from multiple origins: the main application from app.example.com, the API from api.example.com, images from a CDN, analytics from a third-party service, and fonts from Google Fonts. Getting CORS right is essential for all of these cross-origin interactions to work securely.
CORS misconfigurations are consistently ranked among the top API security vulnerabilities. The most common dangerous patterns include: reflecting the Origin header directly in ACAO (trusting any origin), using ACAO: * with credentials, setting ACAO to null (which can be exploited from sandboxed documents), and omitting the Vary: Origin header which can cause CDNs to serve CORS responses meant for one origin to another.
To configure CORS safely, start from a deny-by-default position. Never reflect the Origin request header in the ACAO response header - this effectively disables the Same-Origin Policy for any site. Instead, maintain a whitelist of allowed origins on the server and compare the incoming Origin against it.
If credentials are required (cookies or client certificates), never use Access-Control-Allow-Origin: *. Always specify the concrete origin and set Access-Control-Allow-Credentials: true. Include the Vary: Origin header so that CDNs and browsers cache the correct CORS response for each requesting origin. Limit Access-Control-Allow-Methods and Access-Control-Allow-Headers to only what the API actually needs. Use a short Access-Control-Max-Age (or omit it) to prevent stale preflight responses from being cached too long.
# Safe: specific origin with credentials
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Vary: Origin
# Dangerous: reflecting the request origin
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
# Now evil.com can read authenticated responses1.What does the Same-Origin Policy prevent?
2.When does a browser send a preflight OPTIONS request?
3.What is the danger of reflecting the Origin header in Access-Control-Allow-Origin?