How does the 'Access-Control-Allow-Origin' header work?
Access-Control-Allow-Origin is a CORS (cross-origin resource sharing) header.
When Site A tries to fetch content from Site B, Site B can send an Access-Control-Allow-Origin response header to tell the browser that the content of this page is accessible to certain origins. (An origin is a domain, plus a scheme and port number.) By default, Site B's pages are not accessible to any other origin; using the Access-Control-Allow-Origin header opens a door for cross-origin access by specific requesting origins.
For each resource/page that Site B wants to make accessible to Site A, Site B should serve its pages with the response header:
Access-Control-Allow-Origin: http://siteA.com
Modern browsers will not block cross-domain requests outright. If Site A requests a page from Site B, the browser will actually fetch the requested page on the network level and check if the response headers list Site A as a permitted requester domain. If Site B has not indicated that Site A is allowed to access this page, the browser will trigger the XMLHttpRequest
's error
event and deny the response data to the requesting JavaScript code.Non-simple requests
What happens on the network level can be slightly more complex than explained above. If the request is a "non-simple" request, the browser first sends a data-less "preflight" OPTIONS request, to verify that the server will accept the request. A request is non-simple when either (or both):- using an HTTP verb other than GET or POST (e.g. PUT, DELETE)
- using non-simple request headers; the only simple requests headers are:
Accept
Accept-Language
Content-Language
Content-Type
(this is only simple when its value isapplication/x-www-form-urlencoded
,multipart/form-data
, ortext/plain
)
Access-Control-Allow-Headers
for non-simple headers, Access-Control-Allow-Methods
for non-simple verbs) that match the non-simple verb and/or non-simple headers, then the browser sends the actual request.Supposing that Site A wants to send a PUT request for /somePage
, with a non-simple Content-Type
value of application/json
, the browser would first send a preflight request:
OPTIONS /somePage HTTP/1.1
Origin: http://siteA.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
Note that Access-Control-Request-Method
and Access-Control-Request-Headers
are added by the browser automatically; you do not need to add them. This OPTIONS preflight gets the successful response headers:Access-Control-Allow-Origin: http://siteA.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
When sending the actual request (after preflight is done), the behavior is identical to how a simple request is handled. In other words, a non-simple request whose preflight is successful is treated the same as a simple request (i.e., the server must still send Access-Control-Allow-Origin
again for the actual response).The browsers sends the actual request:
PUT /somePage HTTP/1.1
Origin: http://siteA.com
Content-Type: application/json
{ "myRequestContent": "JSON is so great" }
And the server sends back an Access-Control-Allow-Origin
, just as it would for a simple request:Access-Control-Allow-Origin: http://siteA.com
See Understanding XMLHttpRequest over CORS for a little more information about non-simple requests. How should I be using Access-Control-Allow-Headers?
- See my response to your 3rd point.
- Yes. Your server, your rules.
- Setting both
Access-Control-Allow-Credentials: true
andAccess-Control-Allow-Headers: *
is never useful:
- For security reasons, browsers that support the wildcard in
Access-Control-Allow-Headers
treat the*
value literally in the case of credentialed requests. - Browsers that don't support the wildcard in
Access-Control-Allow-Headers
always treat the*
value literally.
Access-Control-Allow-Headers what does it do exactly and why is it included in a response?
Whenever a request to a server is made from cross origin (say you're requesting an API hosted at Domain A from Domain B), generally the browser blocks them for security reasons. To navigate (this might not be the 'right' word; it's, in fact, the only way of doing) this, there's a mechanism called CORS
(cross origin resource sharing) in which the server has to explicitly say that it wants to allow resource sharing with other domains(origins) too. Now, to 'tell' this, the response has to contain right CORS headers which is Access-Control-Allow-Origin header
.
So in layman terms, it's just a mechanism to inform the browser, "Hey! Look, I deliberately want to send this response to another origin. Don't block it."
So let's say. if the server responds with a wildcard:
Access-Control-Allow-Origin: *,
This means, the resource can be accessed by any domain. Similarly, if the server sends
Access-Control-Allow-Origin: "https://example.com"
Then, this means the resource can be accessed only on this particular domain which is "https://example.com"
This is my understanding.
For more technical details, you may read:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
Request header field Access-Control-Allow-Headers is not allowed by itself in preflight response
When you start playing around with custom request headers you will get a CORS preflight. This is a request that uses the HTTP OPTIONS
verb and includes several headers, one of which being Access-Control-Request-Headers
listing the headers the client wants to include in the request.
You need to reply to that CORS preflight with the appropriate CORS headers to make this work. One of which is indeed Access-Control-Allow-Headers
. That header needs to contain the same values the Access-Control-Request-Headers
header contained (or more).
https://fetch.spec.whatwg.org/#http-cors-protocol explains this setup in more detail.
How do CORS and Access-Control-Allow-Headers work?
Yes, you need to have the header Access-Control-Allow-Origin: http://domain.example:3000
or Access-Control-Allow-Origin: *
on both the OPTIONS response and the POST response. You should include the header Access-Control-Allow-Credentials: true
on the POST response as well.
Your OPTIONS response should also include the header Access-Control-Allow-Headers: origin, content-type, accept
to match the requested header.
Omitting access-control-allow-headers in CORS / preflight response
I’m having trouble finding information explaining what happens if the response to a preflight request or the response to the actual CORS request omitsThose sites are correct: if the preflight request contains anAccess-Control-Allow-Headers
. I have noticed that several sites say this header is required when the request includesAccess-Control-Request-Headers
.
Access-Control-Request-Headers
header, then the response to that request must, at the very least, contain an Access-Control-Allow-Headers
header, or CORS preflight will fail. The actual header value required for CORS preflight to succeed will differ on the basis of which header names were specified in the preflight request.I would like to allow CORS access to my API with any headers, so I’m wondering if omitting this header will do just that and allow all headers.No, omitting the
Access-Control-Allow-Headers
header in the response to the preflight request does not count as a pass.My API requires that you send anClose, but imprecise. Rather, you cannot use the wildcard forAuthorization
header containing an OAuth token and typically the client will also send a cookie. There are sites that say that using*
forAccess-Control-Allow-Headers
only acts as a wildcard whenAccess-Control-Allow-Credentials
isfalse
.
Access-Control-Allow-Headers
if you use Access-Control-Allow-Credentials: true
. See https://fetch.spec.whatwg.org/#http-new-header-syntaxThis seems to put me in a bind since I want the CORS request to include credentials (the Authorization
header and a cookie) but I also want to allow all headers. How can I make this work?
In that case, you cannot use the wildcard. You only have one way: take all the header names listed in the preflight request's Access-Control-Request-Headers
header and reflect them in the response's Access-Control-Allow-Headers
header.A secondary question is why would I want to restrict the request to an allowed set of headers? In my case the API is a GET
which makes no changes on the server. I want allowed origins to always be able to retrieve this information. Under what circumstances would the headers they include play into that?
I may be missing something, but I don't think this is problematic if the set of allowed origins is finite. However, if you allow arbitrary origins, you probably shouldn't allow arbitrary headers on authenticated endpoints. In particular, allowing the Authorization
for arbitrary origins opens the door to client-side, distributed brute-force of Bearer tokens (or whatever the Authorization
normally contains); more about that in https://github.com/whatwg/fetch/issues/251#issuecomment-209265586 Set cookies for cross origin requests
Cross site approach
To allow receiving & sending cookies by a CORS request successfully, do the following.
Back-end (server) HTTP header settings:
Set the HTTP header
Access-Control-Allow-Credentials
value totrue
.Make sure the HTTP headers
Access-Control-Allow-Origin
andAccess-Control-Allow-Headers
are set. Don't use a wildcard*
. When you set the allowed origin make sure to use the entire origin including the scheme, i.e. http is not same as https in CORS.
Cookie settings:
Cookie settings per Chrome and Firefox update in 2021:
SameSite=None
Secure
SameSite=None
, setting Secure
is a requirement. See docs on SameSite and on requirement of Secure. Also note that Chrome devtools now have improved filtering and highlighting of problems with cookies in the Network tab and Application tab.Front-end (client): Set the XMLHttpRequest.withCredentials
flag to true
, this can be achieved in different ways depending on the request-response library used:
ES6 fetch() This is the preferred method for HTTP. Use
credentials: 'include'
.jQuery 1.5.1 Mentioned for legacy purposes. Use
xhrFields: { withCredentials: true }
.axios As an example of a popular NPM library. Use
withCredentials: true
.
Proxy approach
Avoid having to do cross site (CORS) stuff altogether. You can achieve this with a proxy. Simply send all traffic to the same top level domain name and route using DNS (subdomain) and/or load balancing. With Nginx this is relatively little effort.
This approach is a perfect marriage with JAMStack. JAMStack dictates API and Webapp code to be completely decoupled by design. More and more users block 3rd party cookies. If API and Webapp can easily be served on the same host, the 3rd party problem (cross site / CORS) dissolves. Read about JAMStack here or here.
Sidenote
It turned out that Chrome won't set the cookie if the domain contains a port. Setting it for localhost
(without port) is not a problem. Many thanks to Erwin for this tip!
How does Access-Control-Expose-Headers work?
I figured it out. The reason I was receiving my custom header was that I was reading the response headers in the Network tab of Chrome Dev Tools. When I run this script:
fetch('http://127.0.0.1:3000/')
.then(r => {console.log(response.headers.get('foo'))})
It prints null
. So the header is not actually accessible to the fetch request, only to the Dev Tools. Access-Control-Allow-Headers in preflight response
You would set the response headers in the server that you are making the request to.
Related Topics
How to Create a Date Object from String in JavaScript
Typeerror: Cannot Read Property 'Setstate' of Undefined
Check If an Object Implements an Interface at Runtime with Typescript
Convert Unix Timestamp to Date Time (Javascript)
D3.JSON Method Doesn't Return My Data Array
Angularjs Routing 404 Error with HTML5 Mode
Why Does JavaScript's Regex.Exec() Not Always Return the Same Value
How to Find Out with Jquery If an Element Is Being Animated
How to Pass a Flag to Gulp to Have It Run Tasks in Different Ways
Angularjs - UI Router - Programmatically Add States
Differencebetween Decodeuricomponent and Decodeuri
Overriding Interface Property Type Defined in Typescript D.Ts File
JavaScript Es6 Typeerror: Class Constructor Client Cannot Be Invoked Without 'New'
JavaScript Property with Three Dots (...)
Why Does a Simple .*? Non-Greedy Regex Greedily Include Additional Characters Before a Match
Bootstrap: Open Another Modal in Modal