Recently I was working on a project that shares session cookies between multiple origins. In the process, I have encountered several problems related to CORS policy and Set-Cookie settings. The journey to resolve these problems gave me a better understanding of the topic. In this post, I would like to share what I have learned and potentially prevent you from the mistakes I made.
What is CORS Policy? Link to heading
CORS stands for Cross-Origin Resource Sharing. It is “an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources”, from MDN. To put it simply, suppose that you have a backend API hosted on one domain and a frontend application on another. For the frontend app to successfully request resources from the backend API, the correct set of CORS headers must be specified. The backend API should allow requests from the origin on which the frontend is hosted. However, in a case when session cookies are involved, the process of configuring headers becomes tricky.
Access-Control-Allow-Origin header Link to heading
Access-Control-Allow-Origin is the first and the most significant CORS header that needs to be considered. This header is sent by the server in a response to the client’s request. When the client sends its requests, it includes the Origin header with the value of the client’s origin.
The server sets the Access-Control-Allow-Origin header’s value in its response. The value determines if the client is allowed to access the requested resource.
Allow-Control-Allow-Origin can take the following values: a single origin (i.e.,
null. The wildcard symbol allows all origins to access the resource, however, there are some restrictions related to credentials. They will be addressed later in the post. The null value does not have a specific meaning. Sometimes it is used to indicate that the origin is not allowed. However, it should be avoided due to security considerations. The origin value means that the specified origin is allowed to access the resource. When the server wants to allow multiple origins, it first must check the requests Origin header. If the Origin value that arrived is in the list of permitted origins, the server sets the Access-Control-Allow-Origin value to that origin in response.
Access-Control-Allow-Credentials header Link to heading
Access-Control-Allow-Credentials is responsible for specifying whether the server accepts a request with credentials (i.e., cookies or other HTTP Authentication information). This header is also sent by the server in response. The header accepts only the value true. In case the credentials are not needed, the header is omitted entirely instead of using value false. If true is used, the browser will allow reading the response. Otherwise, the browser will automatically reject any response to the request with credentials.
CORS Restrictions Link to heading
A server must not accept requests from any origin and allow credentials at the same time. That is, the following combination is not permitted by modern browsers:
To enable credentials, the server must explicitly specify an origin. Similarly, the Set-Cookie response header would be ignored by modern browsers, if the Access-Control-Allow-Origin value is
*. These restrictions may lead to confusion during the development. Especially considering that Postman ignores these restrictions and behaves differently from browsers.
Session Cookies and Set-Cookie header Link to heading
To save a cookie on the client’s side, the server uses the Set-Cookie header in the response. A single response may contain multiple Set-Cookie headers to send multiple cookies at once. The client can then use these cookies and/or send them back to the server. In the case of session cookies, the client cannot read their contents and can only send them back to the server for authentication purposes. This is achieved by properly specifying attributes of the Set-Cookie header.
A full list of these attributes can be found on MDN. The most significant ones are HttpOnly, Secure, and SameSite. HttpOnly is an optional flag that forbids the client from reading the cookie’s contents. Secure is a flag that requires a request containing a cookie to be made with the HTTP over TLS (SSL). The SameSite attribute specifies the context in which a cookie can be sent. The valid values are Strict, Lax, and None. Strict means that the cookie can be sent only in a first-party context, that is, only from the origin that issued that cookie. Lax allows sending the cookie when the user navigates to the origin where the cookie was issued from a different origin. However, the cookie still cannot be sent on cross-site requests (i.e., load an image or video). None allows sending the cookie in all contexts including cross-site requests.
Set-Cookie Restrictions Link to heading
It should be noted that modern browsers require the Set-Cookie header that contains
SameSite=None to also include the
Secure flag. This requirement may cause difficulties for local development since usually local environment does not have an SSL certificate. One way to get around this is to use
SameSite=Lax for local development. This would work only if the whole development environment is hosted under the single “localhost”. Other approaches include using a reverse proxy or creating a self-signed SSL certificate.