
How to Use SameSite Strict Cookies with Next.js and a Separate Backend
Learn how to use cookies with SameSite=Strict when using a Next.js frontend and a separate backend server. This guide will make the use of Next.js rewrites in order to proxy requests from your frontend to your backend.
Tristan Chin
When working on a project that was using Next.js as frontend with a separate NestJS backend, one of the challenges I came across was keeping my auth cookies with a Same-Site=Strict
configuration. If my Next.js app lives on www.domain.com
and my NestJS backend is on api.domain.com
, then strict cookies cannot be sent from www.domain.com
to api.domain.com
, because they're two different origins.
In this guide, I will show you how you can use strict cookies, even if your Next.js app and backend are on two different origins. This is specifically for Next.js, but here's a spoiler: we'll be using Next.js rewrites as a way to proxy requests. So if your framework supports a similar feature, follow along and just adapt it to yours!
Proxying API Requests with Next.js Rewrites for SameSite Strict Cookies
Next.js' rewrites let you internally redirect any requests to another destination. This happens on the backend, so the browser doesn't even know about it.
In the next.config.ts
file, we can make a route act as a proxy to all our requests. We want to proxy all requests made to www.domain.com/api
to api.domain.com
. To achieve this, head over to your next.config.ts
file and add the following rewrite:
export const nextConfig = {
async rewrites() {
return [
{
source: "/api/:path*",
destination: "https://api.domain.com/:path*",
},
];
},
};
And... that's it! Whenever you need to make a request to your separate backend, just send it to www.domain.com/api
instead of directly to api.domain.com
. From your browser's perspective, this is the same origin, even though the proxied origin is different. Because of that, your strict cookies will be sent over with the request and can be used on your backend!
Proxying requests like that also has a tiny bonus of hiding your actual API domain from public-facing requests. This isn't really security though: security by obfuscation isn't a reliable way of securing APIs from prying eyes. While this may slow down "opportunistic attackers", it's not really hard to guess that api.domain.com
might be your backend...
Caveats When Using SameSite Strict Cookies with a Next.js Frontend
If your project is already using SameSite Lax or None, using a proxy to start using Strict cookies might not be seamless. After all, you just made your cookie policy stricter, so it's expected that some things might break! Some specific flows that used to work with Lax or None might no longer pass the cookies correctly.
Let me know in the comments if you find another caveat, I'm mostly just talking about the one I encountered in my project.
Why SameSite Strict Cookies Fail After OpenID Login Redirects in Next.js
If you're using strict cookies for OpenID logins, you might run into this frustrating issue I had: the first request to your Next.js' server after a successful login doesn’t send the auth cookie, even though you just set it in the response from your backend. Here's why:
Typical OpenID flow (simplified):
- 1. The user clicks “Login with Provider” on
www.domain.com
. - 2. Frontend calls your backend at
api.domain.com
to start the login. - 3. Backend redirects user to the OpenID provider (e.g: Discord).
- 4. After login, the OpenID provider sends the user back to your backend (e.g:
api.domain.com/discord/return
). - 5. Backend sets the auth cookie (
SameSite=Strict
) in the response. - 6. Backend sends the user back to
www.domain.com
.
When the user's browser gets redirected from api.domain.com
back to www.domain.com
at step 6, it loses the ability to send the new cookie on the very next request, because strict cookies don't survive cross-origin navigations.
An example scenario that would break is if you're using server components to pre-render the user's avatar in the header on the server. Unfortunately, for that first request, you'll have to render the header without the avatar (as if the user was logged out) and make a client-side request to the backend after hydration. Subsequent visits and navigations won't need it though, because your browser will send cookies for same-origin requests.
Handling SameSite Cookie Issues After Login with a Temporary Token
When the user logs in, on top of your access and refresh tokens, set a "post-login" token with an extremely short life (10 seconds max) and SameSite=Lax
. This cookie can be used for that first request if the access token isn't being picked up. This exposes users to some security risk for that 10 seconds though. It's up to you to decide if this fits within your standards. Personally, I find a 10 second window to be negligeable. Your user would need to visit a malicious website within those 10 seconds to be exposed. On top of that, Lax can still be secure enough that nothing will happen, which is what I discuss in the next section.
Should You Use SameSite Strict Cookies for Authentication in Next.js?
Although you're probably here specifically for strict cookies, they might not be worth the headache. Most big platforms like YouTube, Facebook and others use SameSite=Lax
for auth cookies. Lax will block most CSRF attacks if you're careful enough (e.g: no mutations on GET
endpoints).
Bottom line: Unless you have some hardcore security requirements, Lax might be sufficient for your needs.