Modifying JWT claims
#
Adding custom claims to the JWT during creationWhen using the JWT feature you can add custom claims to the JWT by using our override feature.
- NodeJS
- GoLang
- Python
import SuperTokens from "supertokens-node";
import Session from "supertokens-node/recipe/session";
SuperTokens.init({
supertokens: {
connectionURI: "...",
},
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
Session.init({
jwt: {
enable: true,
},
override: {
functions: function (originalImplementation) {
return {
...originalImplementation,
createNewSession: async function (input) {
input.accessTokenPayload = {
...input.accessTokenPayload,
role: "user",
};
return originalImplementation.createNewSession(input);
},
};
}
},
})
]
});
import (
"net/http"
"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{
Jwt: &sessmodels.JWTInputConfig{
Enable: true,
},
Override: &sessmodels.OverrideStruct{
Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {
originalCreateNewSession := *originalImplementation.CreateNewSession
(*originalImplementation.CreateNewSession) = func(req *http.Request, res http.ResponseWriter, userID string, accessTokenPayload, sessionData map[string]interface{}, userContext supertokens.UserContext) (sessmodels.SessionContainer, error) {
if accessTokenPayload == nil {
accessTokenPayload = map[string]interface{}{}
}
accessTokenPayload["role"] = "user"
return originalCreateNewSession(req, res, userID, accessTokenPayload, sessionData, 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 Dict, Any, Union
def override_functions(original_implementation: RecipeInterface):
original_implementation_create_new_session = original_implementation.create_new_session
async def create_new_session(request: Any, user_id: str,
access_token_payload: Union[None, Dict[str, Any]],
session_data: Union[None, Dict[str, Any]], user_context: Dict[str, Any]):
if access_token_payload is None:
access_token_payload = {}
access_token_payload['role'] = 'user'
return await original_implementation_create_new_session(request, user_id, access_token_payload, session_data, 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(
jwt=session.JWTConfig(enable=True),
override=session.InputOverrideConfig(
functions=override_functions
)
)
]
)
The above example would add a role
claim to the JWT.
#
Default claims added by SuperTokensSuperTokens adds some claims to JWT payloads:
sub
: The userId is stored in this claimiss
: The issuer URL is stored under this claim. Read more here for information on what the default value is and how to configure it.exp
: The time since epoch (in seconds) after which the JWT is considered as expirediat
: The time since epoch (in seconds) when the JWT was created
#
Updating claims of the JWT post creationAnother method to add custom claims is to use the mergeIntoAccessTokenPayload
function. This allows you to add / edit / delete claims in the JWT even after it is created.
#
Method 1) After session verification- NodeJS
- GoLang
- Python
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js
- NestJS
import express from "express";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
let app = express();
app.post("/updateinfo", verifySession(), async (req, 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";
let fastify = Fastify();
fastify.post("/updateinfo", {
preHandler: verifySession(),
}, async (req, 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 { Controller, Post, UseGuards, 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 fastapi import Depends
from fastapi.responses import PlainTextResponse
from supertokens_python.recipe.session import SessionContainer
@app.post('/update_info')
async def update_info(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-jwt', methods=['POST'])
@verify_session()
def update_info():
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_info(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. - If for a key, you set a value to
null
/nil
/None
when using this function, then that key-value will be removed from the payload if it existed. - The result is that the access token payload is updated in the database and in the user's browser cookies. The change is instantly visible on the frontend and the subsequent backend API calls.
#
Method 2) Without session verificationcaution
Changes to the access token payload via this method are reflected in the session only once the session is refreshed. So use method (1) whenever possible.
- NodeJS
- GoLang
- Python
import Session from "supertokens-node/recipe/session";
async function updateJWT() {
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
sessionHandles, err := session.GetAllSessionHandlesForUser("userId")
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'})