Option 1. Using the access token payload
#
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
def override_functions(original_implementation: RecipeInterface):
original_implementation_create_new_session = original_implementation.create_new_session
async def create_new_session(user_id: str,
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, 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
}
}