Option 1. Using the access token payload
caution
This guide describes how to add custom claims to SuperTokens Access Tokens.
If you are implementing Unified Login, which makes use of OAuth2 Access Tokens, you will have to check the separate guide.
#
Add custom claims to the access token payloadimportant
There are "protected" claims, reserved for standard or supertokens specific use-cases. Trying to overwrite them in createNewSession
or using mergeIntoAccessTokenPayload
will result in errors.
They are: sub
, iat
, exp
, sessionHandle
, refreshTokenHash1
, parentRefreshTokenHash1
, antiCsrfToken
There are two ways to add custom claims to the access token payload:
- During session creation
- Post session creation
#
During session creationThis is the most typical method of adding custom claims. A session is created when a user signs in or signs up, and we can add custom claims to their session by overriding the createNewSession
function as shown below:
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
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);
},
};
},
},
})
]
});
import (
"github.com/supertokens/supertokens-golang/recipe/session"
"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
session.Init(&sessmodels.TypeInput{
Override: &sessmodels.OverrideStruct{
Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
// First we copy the original implementation func
originalCreateNewSession := *originalImplementation.CreateNewSession
// Now we override the CreateNewSession function
(*originalImplementation.CreateNewSession) = func(userID string, accessTokenPayload, sessionDataInDatabase map[string]interface{}, disableAntiCsrf *bool, tenantId string, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
// This goes in the access token, and is available to read on the frontend.
if accessTokenPayload == nil {
accessTokenPayload = map[string]interface{}{}
}
accessTokenPayload["someKey"] = "someValue"
return originalCreateNewSession(userID, accessTokenPayload, sessionDataInDatabase, disableAntiCsrf, tenantId, userContext)
}
return originalImplementation
},
},
}),
},
})
}
from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import session
from supertokens_python.recipe.session.interfaces import RecipeInterface
from typing import Any, Dict, Optional
from supertokens_python.types import RecipeUserId
def override_functions(original_implementation: RecipeInterface):
original_implementation_create_new_session = (
original_implementation.create_new_session
)
async def create_new_session(
user_id: str,
recipe_user_id: RecipeUserId,
access_token_payload: Optional[Dict[str, Any]],
session_data_in_database: Optional[Dict[str, Any]],
disable_anti_csrf: Optional[bool],
tenant_id: str,
user_context: Dict[str, Any],
):
if access_token_payload is None:
access_token_payload = {}
# This goes in the access token, and is available to read on the frontend.
access_token_payload["someKey"] = "someValue"
return await original_implementation_create_new_session(
user_id,
recipe_user_id,
access_token_payload,
session_data_in_database,
disable_anti_csrf,
tenant_id,
user_context,
)
original_implementation.create_new_session = create_new_session
return original_implementation
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework="...",
recipe_list=[
session.init(
override=session.InputOverrideConfig(functions=override_functions)
)
],
)
#
Post session creationIn this method, you can modify the access token payload of an existing session. There are two modes in this:
- With session verification (online mode)
- Without session verification (offline mode)
#
With session verification (online mode)- NodeJS
- GoLang
- Python
- Other Frameworks
Important
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js (Pages Dir)
- Next.js (App Dir)
- NestJS
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" })
});
import Hapi from "@hapi/hapi";
import { verifySession } from "supertokens-node/recipe/session/framework/hapi";
import { SessionRequest } from "supertokens-node/framework/hapi";
let server = Hapi.server({ port: 8000 })
server.route({
path: "/updateinfo",
method: "post",
options: {
pre: [
{
method: verifySession()
},
],
},
handler: async (req: SessionRequest, res) => {
let session = req.session;
await session!.mergeIntoAccessTokenPayload(
{ newKey: "newValue" }
);
return res.response({ message: "successfully updated access token payload" }).code(200);
}
})
import Fastify from "fastify";
import { verifySession } from "supertokens-node/recipe/session/framework/fastify";
import { SessionRequest } from "supertokens-node/framework/fastify";
let fastify = Fastify();
fastify.post("/updateinfo", {
preHandler: verifySession(),
}, async (req: SessionRequest, res) => {
let session = req.session;
await session!.mergeIntoAccessTokenPayload(
{ newKey: "newValue" }
);
res.send({ message: "successfully updated access token payload" });
});
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";
import { SessionEvent } from "supertokens-node/framework/awsLambda";
async function updateinfo(awsEvent: SessionEvent) {
let session = awsEvent.session;
await session!.mergeIntoAccessTokenPayload(
{ newKey: "newValue" }
);
return {
body: JSON.stringify({ message: "successfully updated access token payload" }),
statusCode: 200,
};
};
exports.handler = verifySession(updateinfo);
import KoaRouter from "koa-router";
import { verifySession } from "supertokens-node/recipe/session/framework/koa";
import { SessionContext } from "supertokens-node/framework/koa";
let router = new KoaRouter();
router.post("/updateinfo", verifySession(), async (ctx: SessionContext, next) => {
let session = ctx.session;
await session!.mergeIntoAccessTokenPayload(
{ newKey: "newValue" }
);
ctx.body = { message: "successfully updated access token payload" };
});
import { inject, intercept } from "@loopback/core";
import { RestBindings, post, response } from "@loopback/rest";
import { verifySession } from "supertokens-node/recipe/session/framework/loopback";
import { SessionContext } from "supertokens-node/framework/loopback";
class UpdateInfo {
constructor(@inject(RestBindings.Http.CONTEXT) private ctx: SessionContext) { }
@post("/updateinfo")
@intercept(verifySession())
@response(200)
async handler() {
let session = this.ctx.session;
await session!.mergeIntoAccessTokenPayload(
{ newKey: "newValue" }
);
return { message: "successfully updated access token payload" };
}
}
import { superTokensNextWrapper } from 'supertokens-node/nextjs'
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";
export default async function updateInfo(req: any, res: any) {
await superTokensNextWrapper(
async (next) => {
await verifySession()(req, res, next);
},
req,
res
)
let session = (req as SessionRequest).session;
await session!.mergeIntoAccessTokenPayload(
{ newKey: "newValue" }
);
res.json({ message: "successfully updated access token payload" })
}
import SuperTokens from "supertokens-node";
import { NextResponse, NextRequest } from "next/server";
import { withSession } from "supertokens-node/nextjs";
import { backendConfig } from "@/app/config/backend";
SuperTokens.init(backendConfig());
export async function POST(request: NextRequest) {
return withSession(request, async (err, session) => {
if (err) {
return NextResponse.json(err, { status: 500 });
}
await session!.mergeIntoAccessTokenPayload({ newKey: "newValue" });
return NextResponse.json({ message: "successfully updated access token payload" });
});
}
import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common";
import { SessionContainer } from "supertokens-node/recipe/session";
import { AuthGuard } from './auth/auth.guard';
@Controller()
export class ExampleController {
@Post('example')
@UseGuards(new AuthGuard())
async postExample(@Session() session: SessionContainer): Promise<{ message: string }> {
// For more information about "AuthGuard" and the "Session" decorator please read our NestJS guide.
await session.mergeIntoAccessTokenPayload({
newKey: "newValue",
});
return { message: "successfully updated access token payload" };
}
}
import (
"net/http"
"github.com/supertokens/supertokens-golang/recipe/session"
"github.com/supertokens/supertokens-golang/supertokens"
)
// We assume that you have wrapped this handler with session.VerifySession
func updateInfo(w http.ResponseWriter, r *http.Request) {
// retrieve the session object as shown below
sessionContainer := session.GetSessionFromRequestContext(r.Context())
err := sessionContainer.MergeIntoAccessTokenPayload(map[string]interface{}{"newKey": "newValue"})
if err != nil {
err = supertokens.ErrorHandler(err, r, w)
if err != nil {
// TODO: Send 500 to client
}
return
}
}
- FastAPI
- Flask
- Django
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.session import SessionContainer
from fastapi import Depends
from fastapi.responses import PlainTextResponse
@app.post('/update-access-token-payload')
async def merge_into_access_token_payload(session: SessionContainer = Depends(verify_session())):
await session.merge_into_access_token_payload({ 'newKey': 'newValue' })
return PlainTextResponse(content='success')
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.session import SessionContainer
from flask import g
@app.route('/update-access-token-payload', methods=['POST'])
@verify_session()
def update_access_token_payload():
session: SessionContainer = g.supertokens
session.sync_merge_into_access_token_payload({ 'newKey': 'newValue' })
return 'success'
from supertokens_python.recipe.session.framework.django.asyncio import verify_session
from django.http import HttpRequest
from supertokens_python.recipe.session import SessionContainer
@verify_session()
async def update_access_token_payload(request: HttpRequest):
session: SessionContainer = request.supertokens
await session.merge_into_access_token_payload({ 'newKey': 'newValue' })
- We first require session verification in order to get the session object
- Using that object, we call the
mergeIntoAccessTokenPayload
with new content. This merges the update into the existing object, removing keys set to null in the root of the update object. - The result is that the access token is updated in the user's browser cookies. The change is instantly visible on the frontend and the subsequent backend API calls.
#
Without session verification (offline mode)This method can be used to update the access token payload even if the user is not online.
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
import Session from "supertokens-node/recipe/session";
async function updateAccessTokenPayload() {
let userId = "...";
// we first get all the sessionHandles (string[]) for a user
let sessionHandles = await Session.getAllSessionHandlesForUser(userId);
// we update all the session's Access Token payloads for this user
sessionHandles.forEach(async (handle) => {
let currSessionInfo = await Session.getSessionInformation(handle);
if (currSessionInfo === undefined) {
return;
}
await Session.mergeIntoAccessTokenPayload(handle,
{ newKey: "newValue" }
);
})
}
import "github.com/supertokens/supertokens-golang/recipe/session"
func main() {
// we first get all the sessionHandles (string[]) for a user
tenantId := "public"
sessionHandles, err := session.GetAllSessionHandlesForUser("userId", &tenantId)
if err != nil {
// TODO: handle error
return
}
// we update all the session's access token payloads for this user
for _, handle := range sessionHandles {
sessionInfo, err := session.GetSessionInformation(handle)
if err != nil {
// TODO: handle error
return
}
if sessionInfo == nil {
continue
}
_, err = session.MergeIntoAccessTokenPayload(handle, map[string]interface{}{"newKey": "newValue"})
if err != nil {
// TODO: handle error
return
}
}
}
- Asyncio
- Syncio
from supertokens_python.recipe.session.asyncio import get_all_session_handles_for_user, merge_into_access_token_payload, get_session_information
async def some_func():
# we first get all the session_handles (List[string]) for a user
session_handles = await get_all_session_handles_for_user("userId")
for handle in session_handles:
session_information = await get_session_information(handle)
if session_information is None:
continue
await merge_into_access_token_payload(handle, { 'newKey': 'newValue' })
from supertokens_python.recipe.session.syncio import get_all_session_handles_for_user, merge_into_access_token_payload, get_session_information
# we first get all the session_handles (List[string]) for a user
session_handles = get_all_session_handles_for_user("userId")
for handle in session_handles:
session_information = get_session_information(handle)
if session_information is None:
continue
merge_into_access_token_payload(handle, { 'newKey': 'newValue' })
caution
Changes to the access token payload via this method are reflected in the session only once the session is refreshed.
#
Read the access token payloadOnce the custom payload has been added to the session, you can access it on the backend and frontend in the following ways:
#
Reading the payload on the backend#
With session verification (online mode)This method can be used when the user is online and has sent an API request with their session tokens
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js (Pages Dir)
- Next.js (App Dir)
- NestJS
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
});
import Hapi from "@hapi/hapi";
import { verifySession } from "supertokens-node/recipe/session/framework/hapi";
import { SessionRequest } from "supertokens-node/framework/hapi";
let server = Hapi.server({ port: 8000 });
server.route({
path: "/myApi",
method: "get",
options: {
pre: [
{
method: verifySession()
},
],
},
handler: async (req: SessionRequest, res) => {
let session = req.session;
let accessTokenPayload = session!.getAccessTokenPayload();
let customClaimValue = accessTokenPayload.customClaim
}
})
import Fastify from "fastify";
import { verifySession } from "supertokens-node/recipe/session/framework/fastify";
let fastify = Fastify();
fastify.get("/myApi", {
preHandler: verifySession(),
}, (req, res) => {
let session = req.session;
let accessTokenPayload = session.getAccessTokenPayload();
let customClaimValue = accessTokenPayload.customClaim
});
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";
import { SessionEvent } from "supertokens-node/framework/awsLambda";
async function myApi(awsEvent: SessionEvent) {
let session = awsEvent.session;
let accessTokenPayload = session!.getAccessTokenPayload();
let customClaimValue = accessTokenPayload.customClaim
};
exports.handler = verifySession(myApi);
import KoaRouter from "koa-router";
import { verifySession } from "supertokens-node/recipe/session/framework/koa";
import { SessionContext } from "supertokens-node/framework/koa";
let router = new KoaRouter();
router.get("/myApi", verifySession(), (ctx: SessionContext, next) => {
let session = ctx.session;
let accessTokenPayload = session!.getAccessTokenPayload();
let customClaimValue = accessTokenPayload.customClaim
});
import { inject, intercept } from "@loopback/core";
import { RestBindings, get, response } from "@loopback/rest";
import { verifySession } from "supertokens-node/recipe/session/framework/loopback";
import { SessionContext } from "supertokens-node/framework/loopback";
class GetJWT {
constructor(@inject(RestBindings.Http.CONTEXT) private ctx: SessionContext) { }
@get("/myApi")
@intercept(verifySession())
@response(200)
handler() {
let session = this.ctx.session;
let accessTokenPayload = session!.getAccessTokenPayload();
let customClaimValue = accessTokenPayload.customClaim
}
}
import { superTokensNextWrapper } from 'supertokens-node/nextjs'
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";
export default async function myApi(req: SessionRequest, res: any) {
await superTokensNextWrapper(
async (next) => {
await verifySession()(req, res, next);
},
req,
res
)
let session = req.session;
let accessTokenPayload = session!.getAccessTokenPayload();
let customClaimValue = accessTokenPayload.customClaim
}
import SuperTokens from "supertokens-node";
import { NextResponse, NextRequest } from "next/server";
import { withSession } from "supertokens-node/nextjs";
import { backendConfig } from "@/app/config/backend";
SuperTokens.init(backendConfig());
export async function POST(request: NextRequest) {
return withSession(request, async (err, session) => {
if (err) {
return NextResponse.json(err, { status: 500 });
}
let accessTokenPayload = session!.getAccessTokenPayload();
let customClaimValue = accessTokenPayload.customClaim
return NextResponse.json({});
});
}
import { Controller, Get, UseGuards, Session } from "@nestjs/common";
import { SessionContainer } from "supertokens-node/recipe/session";
import { AuthGuard } from './auth/auth.guard';
@Controller()
export class ExampleController {
@Get('example')
@UseGuards(new AuthGuard())
async postExample(@Session() session: SessionContainer): Promise<any> {
let accessTokenPayload = session.getAccessTokenPayload();
let customClaimValue = accessTokenPayload.customClaim
}
}
import (
"fmt"
"net/http"
"github.com/supertokens/supertokens-golang/recipe/session"
)
// We assume that you have wrapped this handler with session.VerifySession
func myApi(w http.ResponseWriter, r *http.Request) {
// retrieve the session object as shown below
sessionContainer := session.GetSessionFromRequestContext(r.Context())
currAccessTokenPayload := sessionContainer.GetAccessTokenPayload()
customClaimValue := currAccessTokenPayload["customClaim"]
fmt.Println(customClaimValue)
}
- FastAPI
- Flask
- Django
from supertokens_python.recipe.session.framework.fastapi import verify_session
from fastapi import Depends
from supertokens_python.recipe.session import SessionContainer
@app.get('/myApi')
async def my_api(session: SessionContainer = Depends(verify_session())):
access_token_payload = session.get_access_token_payload()
custom_claim_value = access_token_payload["customClaim"]
print(custom_claim_value) # TODO...
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.session import SessionContainer
from flask import g
@app.route('/myApi', methods=['GET'])
@verify_session()
def my_api():
session: SessionContainer = g.supertokens
access_token_payload = session.get_access_token_payload()
custom_claim_value = access_token_payload["customClaim"]
print(custom_claim_value) # TODO...
from supertokens_python.recipe.session.framework.django.asyncio import verify_session
from django.http import HttpRequest
from supertokens_python.recipe.session import SessionContainer
@verify_session()
async def my_api(request: HttpRequest):
session: SessionContainer = request.supertokens
access_token_payload = session.get_access_token_payload()
custom_claim_value = access_token_payload["customClaim"]
print(custom_claim_value) # TODO...
#
Without session verification (offline mode)- NodeJS
- GoLang
- Python
- Other Frameworks
Important
import Session from "supertokens-node/recipe/session";
async function someFunc() {
let userId = "...";
// we first get all the sessionHandles (string[]) for a user
let sessionHandles = await Session.getAllSessionHandlesForUser(userId);
sessionHandles.forEach(async (handle) => {
let currSessionInfo = await Session.getSessionInformation(handle)
if (currSessionInfo === undefined) {
return;
}
let accessTokenPayload = currSessionInfo.customClaimsInAccessTokenPayload;
let customClaimValue = accessTokenPayload.customClaim;
})
}
import (
"fmt"
"github.com/supertokens/supertokens-golang/recipe/session"
)
func main() {
// we first get all the sessionHandles (string[]) for a user
tenantId := "public"
sessionHandles, err := session.GetAllSessionHandlesForUser("userId", &tenantId)
if err != nil {
// TODO: handle error
return
}
// we update all the session's access token payloads for this user
for _, handle := range sessionHandles {
sessionInfo, err := session.GetSessionInformation(handle)
if err != nil {
// TODO: handle error
return
}
accessTokenPayload := sessionInfo.CustomClaimsInAccessTokenPayload
currClaimValue := accessTokenPayload["currClaim"]
fmt.Println(currClaimValue)
}
}
- Asyncio
- Syncio
from supertokens_python.recipe.session.asyncio import get_all_session_handles_for_user, get_session_information
async def some_func():
# we first get all the session_handles (List[string]) for a user
session_handles = await get_all_session_handles_for_user("userId")
for handle in session_handles:
session_information = await get_session_information(handle)
if session_information is None:
continue
current_access_token_payload = session_information.custom_claims_in_access_token_payload
custom_claim_value = current_access_token_payload["customClaim"]
print(custom_claim_value) # TODO..
from supertokens_python.recipe.session.syncio import get_all_session_handles_for_user, get_session_information
# we first get all the session_handles (List[string]) for a user
session_handles = get_all_session_handles_for_user("userId")
for handle in session_handles:
session_information = get_session_information(handle)
if session_information is None:
continue
current_access_token_payload = session_information.custom_claims_in_access_token_payload
custom_claim_value = current_access_token_payload["customClaim"]
print(custom_claim_value) # TODO..
#
Reading the payload on the frontend- ReactJS
- Angular
- Vue
import Session from 'supertokens-web-js/recipe/session';
async function someFunc() {
if (await Session.doesSessionExist()) {
let accessTokenPayload = await Session.getAccessTokenPayloadSecurely();
let customClaimValue = accessTokenPayload.customClaim
}
}
- With React Context
- Without React Context
import React from "react";
import { useSessionContext } from 'supertokens-auth-react/recipe/session';
// Your dashboard component
function Dashboard(props: any) {
let session = useSessionContext();
if (session.loading) {
return null;
}
if (!session.doesSessionExist) {
// TODO
} else {
let { accessTokenPayload } = session;
let customClaimValue = accessTokenPayload.customClaim
// TODO
}
}
import Session from 'supertokens-auth-react/recipe/session';
async function someFunc() {
if (await Session.doesSessionExist()) {
let accessTokenPayload = await Session.getAccessTokenPayloadSecurely();
let customClaimValue = accessTokenPayload.customClaim
}
}
import Session from 'supertokens-web-js/recipe/session';
async function someFunc() {
if (await Session.doesSessionExist()) {
let accessTokenPayload = await Session.getAccessTokenPayloadSecurely();
let customClaimValue = accessTokenPayload.customClaim
}
}