Relaxing the same-origin policy
This note describes how to configure the server to relax the strict same-origin policy imposed by browsers regarding the use of resources from other domains, also known as the CORS protocol.
Sometimes large websites will purposely split the resources it needs into two or more separate domains, in order to improve performance, or provide redundancy, or implement special security requirements. When this is the case, the browser's same-origin policy can be relaxed to allow a script hosted on one domain to access resources hosted on another domain. This is done through cross-origin requests.
Simple CORS requests
Cross-origin requests are only successful when the browser receives positive confirmation from the second domain's server that such a request is allowed. Such confirmation is initiated when a browser determines that a request is potentially unsafe, and sends the second server a request with an
origin header, informing it that the request is coming from another domain. If allowed, the second server sends the confirmation in the form of an
This simple exchange of request and response headers may occur with
POST methods, but only when the request is limited to using these safelisted headers:
- content-type: application/x-www-form-urlencoded
- content-type: multipart/form-data
- content-type: text/plain
When an attempt to issue a cross-origin request is detected by the browser that does not meet the requirements outlined for a simple CORS request, as outlined above, the browser initiates a preflight CORS request. Preflight requests are necessary in order to prevent the real request from even being attempted if the other domain's server is not going to allow it.
In these cases the protocol proceeds with these steps:
- The browser begins by sending an
OPTIONSpreflight request with an
originrequest header and one or more
- The server responds with
access-control-allow-originand one or more
access-control-allow-*headers, informing the browser that it is going to allow the actual request;
- The browser proceeds by sending the actual request method, with the same
- The server carries out the actual request and answers with the actual response.
The browser normally only allows
POST methods to be used in CORS requests. If another method is attempted, the browser sends a
access-control-request-method in the preflight request and the server responds with
access-control-allow-methods to tell the browser which methods are allowed for the specified origin.
The browser will not allow CORS responses to access sensitive data unless the server explicitly tells it otherwise. The server does this by sending an
access-control-allow-credentials response header, in which case the browser is instructed to allow scripts to access cookies and other sensitive data.
Allowing request headers
The browser normally only allows the safelisted headers to be used in CORS requests. If other headers are necessary for the request, the browser sends a
access-control-request-headers in the preflight request and the server responds with
access-control-allow-headers to tell the browser which non-safelisted headers are allowed for the specified origin.
Exposing response headers
The browser normally filters out all CORS response headers that are not in the safelisted headers list. If the server wishes to allow certain other headers to be visible to the browser's script, the server should respond to the preflight request, and the actual request, with an
access-control-expose-headers informing the browser of which other response headers are to be made visible for script requests from the specified origin.
Caching preflight responses
Some applications will issue numerous CORS requests. In order to reduce the number of preflight requests that need to be made, a browser may keep the previous preflight confirmation in its cache. A server can tell the browser how long it can keep any such cache using the
access-control-max-age response header. Subsequent requests to the same domain for the same resource, same method, and same headers can skip the preflight request when it occurs within the window of time specified in that
Things to note regarding the cross-origin resource sharing protocol:
- Not every user-agent is programmed to use CORS. All modern browsers are, but utilities like
wgetare not. Remember this when testing new configurations.
- The decision to use simple CORS versus preflight CORS is made by the browser. The server can handle both, using the same configuration rules.
- CORS does not strengthen security, it weakens security.
- Responding with
access-control-allow-origin: *punches a hole through the same-origin policy, and should be used only with that awareness.
To begin accepting CORS requests, the
cross-origin module must be enabled in the
module section and the
OPTIONS method must be added to the
All other configuration settings are specified using the
cross-origin section, which is subordinate to the
request section. It comprises a collection of entries, where each entry has a path-pattern, an origin hostname, and one or more optional attribute values which specify additional rules to apply to the path pattern.
Refer to the separate note regarding Path Pattern rules.
origin attribute is a single DNS hostname, or a hostname beginning with the
'*' wildcard symbol, or a comma separated list of hostnames, or the special value
'*' by itself.
Each entry may have any combination of these five attributes:
credentialsattribute may be
'false', indicating whether or not the browser should allow access to cookies and other sensitive data.
max-ageattribute is a positive integer value specifying the number of seconds that the browser is allowed to cache the
methodsattribute is a comma-separated list of HTTP methods that the browser is allowed to use in a cross-domain resource request.
headersattribute is a comma-separated list of HTTP request headers that the browser is allowed to send in a cross-domain resource request.
exposeattribute is a comma-separated list of HTTP response headers that the browser should allow the cross-domain script to access.
The response code to a simple CORS request that is not acceptable is
403 with an information header of
The response code to an
OPTIONS preflight request is always
200, whether it is accepted or denied by the server. Requests that are denied omit the
access-control-allow-origin header (per CORS specification), and add an
rw-origin-not-allowed information header.
The response code to an actual CORS request that is not acceptable is
403 with one of these information headers:
rw-origin-not-allowedwhen the requested resource does not match any configured path pattern, or when it matches a configured path pattern but the
originrequest header does not match any hostname listed in the
originattribute's comma-separated list.
rw-method-not-allowedwhen the requested resource matches a configured path pattern but the actual HTTP request method does not match any method listed in the
methodsattribute's comma-separated list.
rw-header-not-allowedwhen the requested resource matches a configured path pattern but the actual HTTP request contains a non-safelisted request header that is not whitelisted in the
headersattribute's comma-separated list.
|file-system-chars||::=||(ALPHA | DIGIT | †)*|
|wildcards||::=||ASTERISK | QUESTION-MARK|
|path-pattern||::=||(SOLIDUS | file-system-chars | wildcards)*|
|delimited-path-pattern||::=||GRAVE-ACCENT path-pattern GRAVE-ACCENT|
|origin-hostname||::=||(ALPHA | DIGIT | FULL-STOP | HYPHEN)*|
|comma-separated-hostnames||::=||((origin-hostname | wildcard-hostname) COMMA)*|
|delimited-hostnames||::=||QUOTATION-MARK (comma-separated-hostnames | any-hostname) QUOTATION-MARK|
|hostname-attribute||::=||ASTERISK 'origin' EQUALS-SIGN delimited-hostnames|
|cors-credentials||::=||ASTERISK 'credentials' EQUALS-SIGN ('true' | 'false')|
|cors-max-age||::=||ASTERISK 'max-age' EQUALS-SIGN [1..86400]|
|comma-separated-methods||::=||('GET' | 'HEAD' | 'PUT' | 'POST' | 'DELETE') COMMA)*|
|cors-methods||::=||ASTERISK 'methods' EQUALS-SIGN comma-separated-methods|
|http-header||::=||(LOWERCASE-ALPHA | DIGIT | HYPHEN)*|
|cors-headers||::=||ASTERISK 'headers' EQUALS-SIGN comma-separated-headers|
|cors-expose||::=||ASTERISK 'expose' EQUALS-SIGN comma-separated-headers|
|cors-entry||::=||delimited-path-pattern hostname-attribute [cors-credentials | cors-max-age | cors-methods | cors-headers | cors-expose]* CR|
|cors-section||::=||'cross-origin' SP LEFT-CURLY-BRACKET CR|
† Legal file system characters vary by platform.
Example 1: Enabling the cross-origin module for CORS
Example 2: Allowing a single host to access anything
Example 3: Allowing subdomains of single host
Example 4: Allowing multiple hosts to access a single directory
Example 5: Wide-open cross-domain access
// Instruct browser to allow any hostname to access this script
Example 6: Allowing scripts to access credentials
`/special-trust-area/*` *origin="https://example.com" *credentials=true
Example 7: Allowing non-standard methods
`/special-trust-area/*` *origin="https://example.com" *methods=GET,HEAD,POST,PUT,DELETE
Example 8: Allowing non safelisted request headers
`/special-trust-area/*` *origin="https://example.com" *headers=x-jwt-key,x-special-value
Example 9: Exposing additional response headers to scripts
`/special-trust-area/*` *origin="https://example.com" *expose=x-security-token,x-access-token
Key points to remember:
- CORS is a weakening of the strict same-origin policy used to safeguard users.
- CORS is needed when applications split their resources into multiple domains.
- Many CORS requests can be configured with simply a path-pattern and an origin.
- The optional CORS configuration attributes are only needed for special cases.