Skip to main content
Which UI do you use?
Custom UI
Pre built UI

5b. Session verification in getServerSideProps

note

This is applicable for when verifying a session in getServerSideProps or getInitialProps.

For this guide, we will assume that we want to pass the logged in user's ID as a prop to a protected route. An easier method to achieve this would be to directly get the user ID from the frontend, but for this example, we will pass it from the server side.

1) We parse the accessToken JWT in getServerSideProps#

important

If using getInitialProps, the method described below applies as well. The only difference is the way the props are returned (see comments in the code).

import jwksClient from "jwks-rsa";
import JsonWebToken from "jsonwebtoken";
import type { JwtHeader, JwtPayload, SigningKeyCallback } from "jsonwebtoken";
import { GetServerSidePropsContext } from 'next';

const client = jwksClient({
jwksUri: "/.well-known/jwks.json",
async fetcher(jwksUri) {
return fetch(jwksUri).then((res) => res.json());
},
});

function getAccessToken(context: GetServerSidePropsContext ): string | undefined {
return context.req.cookies["sAccessToken"];
}

function getPublicKey(header: JwtHeader, callback: SigningKeyCallback) {
client.getSigningKey(header.kid, (err, key) => {
if (err) {
callback(err);
} else {
const signingKey = key?.getPublicKey();
callback(null, signingKey);
}
});
}

async function verifyToken(token: string): Promise<JwtPayload> {
return new Promise((resolve, reject) => {
JsonWebToken.verify(token, getPublicKey, {}, (err, decoded) => {
if (err) {
reject(err);
} else {
resolve(decoded as JwtPayload);
}
});
});
}

/**
* A helper function to retrieve session details on the server side.
*
* NOTE: This function does not use the getSession or verifySession functions from the supertokens-node SDK
* because they can update the access token. These updated tokens would not be
* propagated to the client side, as request interceptors do not run on the server side.
*/
async function getSSRSessionHelper(context: GetServerSidePropsContext): Promise<{
accessTokenPayload: JwtPayload | undefined;
hasToken: boolean;
error: Error | undefined;
}> {
const accessToken = getAccessToken(context);
const hasToken = !!accessToken;
try {
if (accessToken) {
const decoded = await verifyToken(accessToken);
return { accessTokenPayload: decoded, hasToken, error: undefined };
}
return { accessTokenPayload: undefined, hasToken, error: undefined };
} catch (error) {
if (error instanceof JsonWebToken.TokenExpiredError) {
return { accessTokenPayload: undefined, hasToken, error: undefined };
}
return { accessTokenPayload: undefined, hasToken, error: error as Error };
}
}

export async function getServerSideProps(context: GetServerSidePropsContext) {
const { accessTokenPayload, error } = await getSSRSessionHelper(context);

if (error) {
throw error;
}

if (accessTokenPayload === undefined) {
// This occurs if the token has expired or doesn't exist.
// Either way, sending this response prompts the frontend to attempt a session refresh.
//
// Case 1: Token doesn't exist
// - The refresh will fail, and the user will be redirected to the login page.
//
// Case 2: Token has expired
// - The client will call the refresh API and update the session tokens.

return { props: { fromSupertokens: 'needs-refresh' } }
// or return {fromSupertokens: 'needs-refresh'} in case of getInitialProps
}

return {
props: { userId: accessTokenPayload.sub }
}

// or return { userId: accessTokenPayload.sub } in case of getInitialProps
}
caution

Don't use getSession or verifySession here. They might update the session tokens, and since our request interceptors don't run server-side, the updated token won't be propagated to the client side.

2) Doing manual refresh on the frontend#

  • The following will refresh a session if needed, for all your website pages
  • This goes in the /pages/_app.tsx file
/pages/_app.tsx
import React, { useEffect } from "react";
import Session from 'supertokens-auth-react/recipe/session'
import { redirectToAuth } from 'supertokens-auth-react'
import { AppProps } from "next/app";

function MyApp({ Component, pageProps }: AppProps<{fromSupertokens: string}>) {
useEffect(() => {

async function doRefresh() {
// pageProps.fromSupertokens === 'needs-refresh' will be true
// when in getServerSideProps, getSession throws a TRY_REFRESH_TOKEN
// error.

if (pageProps.fromSupertokens === 'needs-refresh') {
if (await Session.attemptRefreshingSession()) {
// post session refreshing, we reload the page. This will
// send the new access token to the server, and then
// getServerSideProps will succeed
location.reload()
} else {
// the user's session has expired. So we redirect
// them to the login page
redirectToAuth()
}
}
}
doRefresh()

}, [pageProps.fromSupertokens])

if (pageProps.fromSupertokens === 'needs-refresh') {
// in case the frontend needs to refresh, we show nothing.
// Alternatively, you can show a spinner.

return null
}

// the below is already there by default
return <Component {...pageProps} />
}

export default MyApp

3) Consume the userId returned by getServerSideProps in your component#

On success, getServerSideProps returns

{
// Refer to Step 1)
props: { userId: accessTokenPayload.sub }
}

Therefore, the associated page can access the userId like:

export default function Home(props: any) {
let userId = props.userId;
}