4. Checking for sessions in frontend routes
Learn to protect frontend routes by checking user sessions and handling session-related events.
Protecting a website route means that it cannot be accessed unless a user is signed in. If a non signed in user tries to access it, they will be redirected to the login page.
Sessions with Client Components
Lets create a client component for the / route of our website.
Using the SessionAuth wrapper component
"use client";
import { SessionAuth } from "supertokens-auth-react/recipe/session";
export const HomeClientComponent = () => {
return (
<SessionAuth>
<div>Hello world</div>
</SessionAuth>
);
};SessionAuth is a component exposed by the SuperTokens React SDK, it checks if a session exists and if it does not exist it will redirect the user to the login page. It also does session claim checking on the frontend and take appropriate action if the claim validators fail. For example, if you have set the email verification recipe to be "REQUIRED", and the user’s email is not verified, this component will redirect the user to the email verification page.
Using useSessionContext
"use client";
import { useSessionContext } from "supertokens-auth-react/recipe/session";
export const HomeClientComponent = () => {
const session = useSessionContext();
if (session.loading) {
return <div>Loading...</div>;
}
if (session.doesSessionExist === false) {
return <div>Session does not exist</div>;
}
return (
<div>
<div>
<p>
Client side component got userId: {session.userId}
<br />
</p>
</div>
</div>
);
};useSessionContext lets you access the session information on the client side using the React Context API. session.loading indicates if the session is currently being loaded into the context, this will also refresh the session for you if it is expired. You can use session.doesSessionExist to check if a valid session exists and handle it accordingly.
Sessions with Server Components
Creating some helper Components
Creating a wrapper around SessionAuth
Let’s say we want to protect the home page of your website (/ route). First we will create a wrapper around the SessionAuth component to add the "use client" directive on top.
"use client";
import React, { useState, useEffect } from "react";
import { SessionAuth } from "supertokens-auth-react/recipe/session";
type Props = Parameters<typeof SessionAuth>[0] & {
children?: React.ReactNode | undefined;
};
export const SessionAuthForNextJS = (props: Props) => {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setLoaded(true);
}, []);
if (!loaded) {
return props.children;
}
return <SessionAuth {...props}>{props.children}</SessionAuth>;
};This is a client component that renders just its children on the server side and renders the children wrapped with SessionAuth on the client side. This way, the server side returns the page content, and on the client, the same page content is wrapper with SessionAuth which will handle session related events on the frontend - for example, if the user’s session expires whilst they are on this page, then SessionAuth will auto redirect them to the login page.
Creating the TryRefreshComponent
This component will refresh the user’s session if their current session has expired.
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import Session from "supertokens-auth-react/recipe/session";
import SuperTokens from "supertokens-auth-react";
export const TryRefreshComponent = () => {
const router = useRouter();
const [didError, setDidError] = useState(false);
useEffect(() => {
/**
* `attemptRefreshingSession` will call the refresh token endpoint to try and
* refresh the session. This will throw an error if the session cannot be refreshed.
*/
void Session.attemptRefreshingSession()
.then((hasSession) => {
/**
* If the user has a valid session, we reload the page to restart the flow
* with valid session tokens
*/
if (hasSession) {
router.refresh();
} else {
SuperTokens.redirectToAuth();
}
})
.catch(() => {
setDidError(true);
});
}, [router]);
/**
* We add this check to make sure we handle the case where the refresh API fails with
* an unexpected error
*/
if (didError) {
return <div>Something went wrong, please reload the page</div>;
}
return <div>Loading...</div>;
};Using SessionAuthForNextJS and checking for sessions
We then create a server component that can check if the session exists and return any session information we may need:
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { getSSRSession } from "supertokens-node/nextjs";
import { TryRefreshComponent } from "./tryRefreshClientComponent";
import { SessionAuthForNextJS } from "./sessionAuthForNextJS";
import { ensureSuperTokensInit } from "../config/backend";
ensureSuperTokensInit();
export async function HomePage() {
const cookieStore = await cookies();
const { accessTokenPayload, hasToken, error } = await getSSRSession(cookieStore.getAll());
if (error) {
console.error("Unable to read the SSR session", { component: "HomePage" });
return <div role="alert">Unable to verify your session. Please try again.</div>;
}
// `accessTokenPayload` will be undefined if it the session does not exist or has expired
if (accessTokenPayload === undefined) {
if (!hasToken) {
/**
* This means that the user is not logged in. If you want to display some other UI in this
* case, you can do so here.
*/
return redirect("/auth");
}
/**
* This means that the session does not exist but we have session tokens for the user. In this case
* the `TryRefreshComponent` will try to refresh the session.
*
* To learn about why the 'key' attribute is required refer to: https://github.com/supertokens/supertokens-node/issues/826#issuecomment-2092144048
*/
return <TryRefreshComponent key={Date.now()} />;
}
/**
* SessionAuthForNextJS will handle proper redirection for the user based on the different session states.
* It will redirect to the login page if the session does not exist etc.
*/
return (
<SessionAuthForNextJS>
<div>Your user id is: {accessTokenPayload.sub}</div>
</SessionAuthForNextJS>
);
}The TryRefreshComponent is a client component that checks if a session exists and tries to refresh the session if it is expired.
And then we can modify the /app/page.tsx file to use our server component
import styles from "./page.module.css";
import { HomePage } from "./components/home";
export default function Home() {
return (
<main className={styles.main}>
<HomePage />
</main>
);
}Sessions with Client Components
Checking for sessions in client components involves:
- Using the
Sessionrecipe to manually check if a session exists, rendering some default UI while you check. - Render your UI if a session exists.
To learn more about how to do this refer to this page.
Sessions with Server Components
Creating a helper component for session refreshing
Lets start by creating a component that will refresh the session if it exists and has expired.
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import Session from "supertokens-web-js/recipe/session";
export const TryRefreshComponent = () => {
const router = useRouter();
const [didError, setDidError] = useState(false);
useEffect(() => {
void Session.attemptRefreshingSession()
.then((hasSession) => {
if (hasSession) {
router.refresh();
} else {
/**
* This means that the session is expired and cannot be refreshed.
* In this example we redirect the user back to the login page.
*/
router.replace("/auth");
}
})
.catch(() => {
setDidError(true);
});
}, [router]);
if (didError) {
return <div>Something went wrong, please reload the page</div>;
}
return <div>Loading...</div>;
};Session.attemptRefreshingSession will call the refresh endpoint. hasSession will be:
trueif the session was refreshedfalseif the session could not be refreshed
Modify home page to check for sessions
Lets modify the Home page server component we created earlier:
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { getSSRSession } from "supertokens-node/nextjs";
import { TryRefreshComponent } from "./tryRefreshClientComponent";
import { ensureSuperTokensInit } from "../config/backend";
ensureSuperTokensInit();
export async function HomePage() {
const cookieStore = await cookies();
const { accessTokenPayload, hasToken, error } = await getSSRSession(cookieStore.getAll());
if (error) {
console.error("Unable to read the SSR session", { component: "HomePage" });
return <div role="alert">Unable to verify your session. Please try again.</div>;
}
// `accessTokenPayload` will be undefined if it the session does not exist or has expired
if (accessTokenPayload === undefined) {
if (!hasToken) {
/**
* This means that the user is not logged in. If you want to display some other UI in this
* case, you can do so here.
*/
return redirect("/auth");
}
/**
* This means that the session does not exist but we have session tokens for the user. In this case
* the `TryRefreshComponent` will try to refresh the session.
*
* To learn about why the 'key' attribute is required refer to: https://github.com/supertokens/supertokens-node/issues/826#issuecomment-2092144048
*/
return <TryRefreshComponent key={Date.now()} />;
}
return <div>Your user id is: {accessTokenPayload.sub}</div>;
}The TryRefreshComponent is a client component that checks if a session exists and tries to refresh the session if it is expired.
And then we can modify the /app/page.tsx file to use our server component
import styles from "./page.module.css";
import { HomePage } from "./components/home";
export default function Home() {
return (
<main className={styles.main}>
<HomePage />
</main>
);
}