Sharing cookies with JWTs across different domains and ports
August 30, 2021
I’ve written numerous posts about JWT authentication, touching on comparisons to session based auth, implementation solutions, and security. It’s an interesting aspect of the web for me as this authentication approach can be handled in many different ways. An important decision to make when dealing with JWTs is where to store the JWT, for which the solution is usually to store in a cookie or local storage.
This post is not to go in depth about JWT storage as this has been done in great detail elsewhere. Instead, I’m focussing on using cookies to store JWTs across different domains and ports for authenticating fetch requests.
Cookies aren’t shared between different domains without an explicit CORS origin policy
Sharing cookies between sites on the same domain and even subdomain is easy enough when navigating the web through a browser UI. The rules in this situation are fairly clear.
However when performing XHR (Fetch) requests, sharing cookies between different websites using a browser requires configuration due to CORS (Cross-Origin Resource Sharing). This applies to completely different websites like https://www.site.com
and https://othersite.com
, but also applies to different subdomains of the same site, as well as different ports and protocols of the same site. So by default, cookies can’t be shared between https://api.site.com
and https://www.site.com
.
This problem even affects localhost development with websites using local host file driven URLs with different ports.
CORS (Cross-Origin Resource Sharing) is a protocol which allows the server to define other origins which a web browser is permitted to send requests. With https://www.site.com
and https://othersite.com
, by default users browsing these two sites can’t send requests to the other server because it could be a security risk to allow any browser to poke different endpoints willy nilly. If this was attempted, it would show the dreaded CORS error which brings back memories of my early days as a developer:
To overcome this, the server is required to set a CORS Policy containing an Access-Control-Allow-Origin
response header, which lists other domains that browsers are allowed to send requests. One way to set up a CORS policy is to use the wildcard for this response header:
Access-Control-Allow-Origin: *
Setting this wildcard allows any origin domain to send requests to the server from within the users browser. This can be good if the API you’re using is truly public, or if the requests being sent to the server are coming from a hybrid mobile app (as these apps are using embedded web views making HTTP requests).
However, using a wildcard will mean cookies are not sent along with requests.
Servers must be set to allow credentials like cookies and JWT headers to be sent across different domains
Another critical CORS header required for sharing cookies between different domains is the Access-Control-Allow-Credentials
response header which must be set to true
:
Access-Control-Allow-Credentials: true
However setting this header to true
is ignored by browsers if the Access-Control-Allow-Origin
is set to a wildcard (*). This is a security feature of the protocol which means sending credentials such as cookies or JWT headers between different domains must be explicityly defined.
Instead of using a wildcard for the allowed origin policy, the server must set allowed domains explicitly. Implementing this response header means that Access-Control-Allow-Credentials
can be set, and cookies can be used across domains:
Access-Control-Allow-Origin: 'https://othersite.com'
Access-Control-Allow-Credentials: true
In our example earlier with https://www.site.com
and https://othersite.com
, the above headers would be set on https://www.site.com
to allow https://othersite.com
to send requests which include headers and cookies containing JWTs.
XHR or libraries like Fetch and Axios require credentials options set client side
As well as setting the allow origin and allow credentials headers on the server, one more step must be taken to ensure cookies are shared between multiple domains, which is to enable the sharing of credentials on the client side using HXR requests.
A typical example might be when using Axios to send a request to a standard API which hits an authenticated endpoint. We want the JWT to be inside the cookie in this request but the request is being sent from the site https://www.site.com
, and hitting the API at https://api.site.com
:
axios.get("https://api.site.com/v1/get-widgets", { withCredentials: true })
In a similar way as discussed earlier on the server, the client must also be explicit in declaring that the site wishes to share credentials in cookies or JWT headers. Without this withCredentials
option, the cookie will not be shared.
Senior Engineer at Haven