Skip to main content

Claim validation

Overview

SuperTokens provides two approaches for managing access control:

  1. Session Claims: An abstraction that includes automatic validation and refresh capabilities
  2. Access Token Payload: A basic way to check the token payload

In most of the cases the recommended way is to use session claims. You can use next table to understand the differences between the two approaches.

FeatureSession ClaimsAccess Token Payload
Store simple static data
Built-in validation
Automatic refresh mechanism
Graceful validation failure handling
Lightweight implementation
No validation overhead

This guide shows you how to use each method.

References

Before you start

This guide only applies to scenarios which involve SuperTokens Session Access Tokens.

Using session claims

SuperTokens sessions have a property called accessTokenPayload. This is a JSON object which you can access on the frontend and backend. The key-values in this JSON payload refer to claims.

1. Create a custom claim

import { BooleanClaim } from "supertokens-node/recipe/session/claims";

const SecondFactorClaim = new BooleanClaim({
key: "2fa-completed",
fetchValue: () => false,
});

2. Add claim validators

Backend global validation

import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";

SuperTokens.init({
recipeList: [
Session.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
getGlobalClaimValidators: async function (input) {
return [...input.claimValidatorsAddedByOtherRecipes, SecondFactorClaim.validators.isTrue()];
},
};
},
},
}),
],
});

Backend route-specific validation

import express from "express";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { UserRoleClaim } from "supertokens-node/recipe/userroles";

let app = express();

app.post(
"/admin-only",
verifySession({
overrideGlobalClaimValidators: async (globalValidators) => [
...globalValidators,
UserRoleClaim.validators.includes("admin"),
],
}),
async (req, res) => {
// Only admin users can access this endpoint
},
);

Frontend validation

import React from "react";
import { SessionAuth } from "supertokens-auth-react/recipe/session";
import { UserRoleClaim } from "supertokens-auth-react/recipe/userroles";

const AdminRoute = (props: React.PropsWithChildren<any>) => {
return (
<SessionAuth
overrideGlobalClaimValidators={(globalValidators) => [
...globalValidators,
UserRoleClaim.validators.includes("admin"),
]}
>
{props.children}
</SessionAuth>
);
};

3. Handle validation failures

Backend custom error handling

import { Error as STError } from "supertokens-node/recipe/session";

if (roles === undefined || !roles.includes("admin")) {
throw new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [
{
id: UserRoleClaim.key,
},
],
});
}

Frontend redirection

const AdminRoute = (props: React.PropsWithChildren<any>) => {
return (
<SessionAuth
overrideGlobalClaimValidators={(globalValidators) => [
...globalValidators,
{
...UserRoleClaim.validators.includes("admin"),
onFailureRedirection: () => "/not-an-admin",
},
]}
>
{props.children}
</SessionAuth>
);
};

Using the Access Token Payload

The access token payload is a simple way to store custom data that needs to be accessible on both the frontend and the backend.

1. Add Custom Claims to the Access Token Payload

important

The access token payload has a set of default claims that can not be overwritten. They reserve these for standard or SuperTokens specific use-cases. Those claims are: sub, iat, exp, sessionHandle, refreshTokenHash1, parentRefreshTokenHash1, antiCsrfToken

Trying to overwrite these values results in errors in the authentication flow process.

You can add custom claims to the access token payload in two ways:

During session creation

import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";

SuperTokens.init({
supertokens: {
connectionURI: "...",
},
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
Session.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
createNewSession: async function (input) {
let userId = input.userId;

// This goes in the access token, and is available to read on the frontend.
input.accessTokenPayload = {
...input.accessTokenPayload,
someKey: "someValue",
};

return originalImplementation.createNewSession(input);
},
};
},
},
}),
],
});

Post Session Creation

import express from "express";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";

let app = express();

app.post("/updateinfo", verifySession(), async (req: SessionRequest, res) => {
let session = req.session;
await session!.mergeIntoAccessTokenPayload({ newKey: "newValue" });
res.json({ message: "successfully updated access token payload" });
});

2. Read the Access Token Payload

On the backend

import express from "express";
import { verifySession } from "supertokens-node/recipe/session/framework/express";

let app = express();

app.get("/myApi", verifySession(), async (req, res) => {
let session = req.session;
let accessTokenPayload = session.getAccessTokenPayload();
let customClaimValue = accessTokenPayload.customClaim;
});

On the frontend

import Session from "supertokens-auth-react/recipe/session";

async function someFunc() {
if (await Session.doesSessionExist()) {
let accessTokenPayload = await Session.getAccessTokenPayloadSecurely();
let customClaimValue = accessTokenPayload.customClaim;
}
}