Session verification during server-side rendering
Enable secure session verification during server-side rendering with cookie-based sessions.
Overview
In Server Side Rendering (SSR) scenarios, the session verification process is slightly different. Check the following guide to understand how to adjust the flow to work with SSR.
Before you start
For an ordinary browser navigation, the browser can send cookie-based session tokens to the server performing SSR. It
cannot add SuperTokens’ header-transfer token to that navigation. Cookie transfer is the default; the cookie’s domain,
path, SameSite, and Secure attributes must allow it to reach the SSR server.
Steps
1. Enable sharing of cookies across subdomains
If your API layer and website are on different subdomains (like example.com and api.example.com), then by default, the session tokens attach only to api.example.com.
Change this to ensure that the session tokens attach to .example.com and the access token cookie goes to your web server on example.com.
Enable this by setting the cookieDomain configuration on the backend.
2. Verify the session
Prefer the released session verification API for your backend framework. For example, the Node.js Next.js integration
exports getSSRSession, which accepts the request’s cookies and validates the access-token signature, expiry, and payload
shape. It does not run global claim validators or perform an authoritative database check for session revocation. Treat its
payload as authenticated claims, not as a complete authorization decision: explicitly validate every claim required by the
page, such as tenant, role, permission, email verification, or MFA state.
Before rendering sensitive protected data for which immediate revocation matters, call an authoritative backend/API
endpoint that verifies the session with database checking enabled. Other backend frameworks can use getSession or
verifySession, provided the SSR response propagates any token updates that the session API attaches. If your platform has
no released SuperTokens SDK, follow the strict requirements in the
manual verification fallback,
not signature-only JWT verification.
If the access token is missing, invalid, or expired, redirect the user to a
/refresh-session?redirectBack=<current route> page. You can use another local path on your website instead.
On the /refresh-session page, you want to call the attemptRefreshingSession function (from the client side).
This function attempts to refresh the session.
If it succeeds, it returns true.
If it fails, it returns false.
If it returns true, you want to redirect the user back to the page they were on.
If it returns false, you want to redirect the user to the login page.
3. Implement the refresh-session flow (/refresh-session page)
On this path, attempt to refresh the session. This can produce either result:
- Success: The frontend gets new access and refresh tokens. Redirect the user to the validated local path in the
redirectBackquery parameter. - Failure: The session has expired or the backend has revoked it. Redirect the user to the login page.
Below is the code snippet that you can use on the /refresh-session path on the frontend
import React from "react";
import Session from "supertokens-auth-react/recipe/session";
import SuperTokens from "supertokens-auth-react";
export function AttemptRefresh() {
React.useEffect(() => {
let cancel = false;
Session.attemptRefreshingSession().then((success) => {
if (cancel) {
// component has unmounted somehow..
return;
}
if (success) {
// we have new session tokens, so we redirect the user back
// to where they were.
const urlParams = new URLSearchParams(window.location.search);
const redirectBack = new URL(urlParams.get("redirectBack") ?? "/", window.location.origin);
window.location.href =
redirectBack.origin === window.location.origin
? `${redirectBack.pathname}${redirectBack.search}${redirectBack.hash}`
: "/";
} else {
// we redirect to the login page since the user
// is now logged out
SuperTokens.redirectToAuth();
}
});
return () => {
cancel = true;
};
}, []);
return null;
}import Session from "supertokens-web-js/recipe/session";
function attemptRefresh() {
Session.attemptRefreshingSession().then((success) => {
if (success) {
// we have new session tokens, so we redirect the user back
// to where they were.
const urlParams = new URLSearchParams(window.location.search);
const redirectBack = new URL(urlParams.get("redirectBack") ?? "/", window.location.origin);
window.location.href =
redirectBack.origin === window.location.origin
? `${redirectBack.pathname}${redirectBack.search}${redirectBack.hash}`
: "/";
} else {
// we redirect to the login page since the user
// is now logged out
window.location.href = "/login";
}
});
}import Session from "supertokens-web-js/recipe/session";
function attemptRefresh() {
Session.attemptRefreshingSession().then((success) => {
if (success) {
// we have new session tokens, so we redirect the user back
// to where they were.
const urlParams = new URLSearchParams(window.location.search);
const redirectBack = new URL(urlParams.get("redirectBack") ?? "/", window.location.origin);
window.location.href =
redirectBack.origin === window.location.origin
? `${redirectBack.pathname}${redirectBack.search}${redirectBack.hash}`
: "/";
} else {
// we redirect to the login page since the user
// is now logged out
window.location.href = "/login";
}
});
}function attemptRefresh() {
supertokensSession.attemptRefreshingSession().then((success) => {
if (success) {
// we have new session tokens, so we redirect the user back
// to where they were.
const urlParams = new URLSearchParams(window.location.search);
const redirectBack = new URL(urlParams.get("redirectBack") ?? "/", window.location.origin);
window.location.href =
redirectBack.origin === window.location.origin
? `${redirectBack.pathname}${redirectBack.search}${redirectBack.hash}`
: "/";
} else {
// we redirect to the login page since the user
// is now logged out
window.location.href = "/login";
}
});
}Why trigger the refresh session flow instead of redirecting the user to the login page directly?
Two reasons why JWT verification can fail are:
- The session tokens were not passed from the frontend: This can happen if the route is accessed when the user has logged out.
- The session tokens pass from the frontend, but the JWT has expired.
If the user goes to the login page directly, then in the second case, the frontend redirects the user back to the current route (since the refresh token is still valid), causing an infinite loop. To counteract this issue, redirect the user to a refresh page, which creates a new access token. In the first case, when the user is actually logged out, the refreshing fails, and they go to the login page anyway.
Can verifySession or getSession be used during SSR?
Yes. A released, read-only SSR helper such as Node.js Next.js getSSRSession can authenticate the access token, but the
application must still validate the authorization claims required by the page. The helper does not run global claim
validators or authoritatively check database revocation. For sensitive data that requires immediate revocation, verify the
session through an authoritative backend/API endpoint with database checking before rendering.
You can also use verifySession or getSession, but those APIs may attach rotated or updated tokens to the response. The
SSR server must propagate those updates because frontend SDK network interceptors do not run for the browser’s navigation
request. If the SSR process should not receive SuperTokens Core credentials, use the read-only helper for authentication
and an authoritative backend/API check for sensitive authorization, or use a manual verifier that meets every requirement
in the linked fallback guide.