Often, servers want to save some client specific information in the browser. This helps the server to serve more personalized experiences.
Have you ever noticed the items you add in your shopping cart stays the same after you reopen your browser? Or YouTube remembers how much you have watched a particular video?
These type of customization can only be possible, if the server save some information about the user in the browser. The browser send this information with every request to the server.
What is a Cookie
A cookie is a small block of information stored in the browser.
A server can generate a cookie to store some client specific information in the browser. The server sends the cookie with an HTTP
response with a Set-Cookie
header.
Once a cookie is set, The browser sends the cookie with each request to the server.
There are many types of cookie present. Let’s get a brief overview about some important ones.
Session cookie
When you create a new tab in the browser, a new session starts. The session ends when you close the tab.
A session cookie persists only during the session duration. This type of cookie doesn’t have any expiration date. After each session, browser delete these cookies.
Persistent cookie
As the name suggests, persistent cookies are persistent in nature. The browser doesn’t delete the persistent cookie after each session.
Persistent cookie comes with an expiration date or max-age attribute. When the time pass, browser automatically delete these cookies.
Same site attributes of a Cookie
This is an example of Set-Cookie
header in an HTTP
response.
Set-Cookie: key=value; Expires=Thu, 2 May 2024 07:28:00 GMT; Secure; HttpOnly; SameSite=Strict
Focus on the Expires
and SameSite
word. These are called cookie attributes.
In this blog, we are discussing the SameSite
attribute in a cookie.
Same site attribute in a cookie restrict browser to send the cookie in cross-site requests. The SameSite
attribute accepts one of the 3 values, Strict
, lax
and none
. Let’s understand them briefly.
Same site strict
Cookie with SameSite=strict
attribute, restrict browser to send with every cross site requests. Even if a user follows a URL from an other website, the same site strict cookie is not sent by the browser.
Same site lax
Cookie with SameSite=Lax
attribute take a balance approach. When a cookie is set to SameSite=Lax
, the browser sends the cookie if someone follow a URL from other website. But the cookie is not sent in other 3rd party requests like fetching an image or scripts from other websites.
This is the default value for SameSite
attribute.
Same site None
Cookie with SameSite=None
attribute doesn’t give any security. This cookie is sent with every requests by the browser.
Understand the OAuth flow
Let’s take an example to understand the situation. You have 3 HTTP
endpoint defined in your server.
/auth/login
: User send a GET request to this endpoint. Server generate an OAuth URL and redirect to the OAuth provider website to complete the authentication./auth/callback
: After user completes the authentication, OAuth provider redirect the user to this endpoint with the authenticationcode
in the query parameter. Server exchange this code for an access token and refresh token./user/dashboard
: Personalized dashboard for each user. This endpoint is only accessible if the user is authenticated with the OAuth provider.
To identify if a user is authenticated or not, server send a Cookie to the browser after getting the access token in auth/callback
endpoint. Then server redirect the successfully authenticated user to the /user/dashboard
page.
When a user visits the /user/dashboard
page, the server reads the cookie associated with the request. If the cookie is present, only then the server gives access to the /user/dashboard
page to the visitor.
Gotchas with strict same site cookies.
This authentication flow works well when you set the SameSite
attribute of your cookie to Lax
or None
. In this case, browser send the authentication cookie to the /user/dashboard
page when redirected from the /auth/callback
page.
Gotchas start appearing when you set the SameSite
attribute to Strict
. Earlier in this blog, you have read that cookie with SameSite=Strict
is only sent by the browser in first party context.
But remember that once the user completes the authentication process in OAuth provider’s website, they are redirected to the /auth/callback
then finally /user/dashboard
page. The OAuth provider website is in 3rd party context and server endpoints are 1st party context.
GET /user/login -(redirect)-> Oauth provider website
GET OAuth provider -(redirect)-> /auth/callback -(redirect)-> /auth/dashboard
As you can see above that, the second GET
request originate from the 3rd party context. Therefore, the browser doesn’t send the authentication cookie with SameSite=Strict
attribute. Although that cookie is set by the /auth/callback
response.
As a result, instead of the user is authenticated and the authentication cookie is set, the browser doesn’t send the cookie with the redirect request to /user/dashboard
page and the user get a 401
Unauthorized response.
Solve the same site cookie problems
To solve this problem, we should break the 2nd redirect chain and add an intermediate page.
GET /user/login -(redirect)-> Oauth provider website
GET OAuth provider -(redirect)-> /auth/callback -(redirect)-> /intermediate
GET /intermediate -(JS redirect)-> /user/dashboard
Instead of directly redirecting the user to the /user/dashboard
page in the 2nd request, load the /intermediate
page as a redirect from /auth/callback
.
Now add a Javascript snippet in the /intermediate
page to make a client side redirect to the /user/dashboard
page. As this 3rd request originates from a first party context, browser send the authentication cookie with the request and the user can successfully access the /user/dashboard
page.
setTimeout(() => {
window.location.href = "/user/dashboard";
}, 3000);
This script redirect the site to /user/dashboard
in 3 seconds.
Other solution
On the contrary, you can set the SameSite=Lax
attribute to the authentication cookie. This change make browser to send the authentication cookie in redirect requests originates from 3rd party contexts.
This solution doesn’t need you to create a separate /intermediate
page, but is less secure than SameSite=Strict
cookie.
We have previously discussed the nature of SameSite=Lax
attribute. Browser send this cookie when a user visits the website but blocks the cookie in other 3rd party request.
Conclusion
Same site attribute is one of the security measure you can use to secure your website. In addition to this, there are other important cookie attributes like HTTPOnly
and Secure
which further improve the security of your website. Learn to use these attributes and make a balance between security and usability for your application.