Skip to main content

2) Session customisation

The backend quick setup for the EmailPassword recipe, instructs you to add Session.init() on the backend's reicpeList array as well. So when the user signs up or logs in with the emailpassword recipe, a session is created at the end of it.

However, we do not want the user to be able to use the app unless they have also completed the second login challenge of verifying their phone number via an OTP. From the backend's point of view, this means that they should not be able to query any of the application's APIs route until they have finished both of the login challenges.

In order to achieve this, we will store information in the session's access token payload to indicate if they have finished both the login challenges or not. Then the API routes can only give access to the user if the session payload indicates that both the challenges have been completed. The information we will stored as a custom claim:

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

export const PhoneVerifiedClaim = new BooleanClaim({
fetchValue: () => false,
key: "phone-verified",
});

On session creation, after the phone password login (just the first challenge) we will set this claim to false. And then once we have verified the phone number, we will mark this as true. The API routes will give access to the user only if this claim is true.

To do this, we override the createNewSession recipe function from the Session recipe:

import EmailPassword from "supertokens-node/recipe/emailpassword";
import Session from "supertokens-node/recipe/session";
import supertokens from "supertokens-node";
import { PhoneVerifiedClaim } from "./phoneVerifiedClaim";

supertokens.init({
framework: "...",
appInfo: { /*...*/ },
recipeList: [
EmailPassword.init({ /* ... */ }),
Session.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
createNewSession: async function (input) {
// we also get the phone number of the user and save it in the
// session so that the OTP can be sent to it directly
let userInfo = await supertokens.getUser(input.userId, input.userContext);
return originalImplementation.createNewSession({
...input,
accessTokenPayload: {
...input.accessTokenPayload,
...PhoneVerifiedClaim.build(input.userId, input.recipeUserId, input.tenantId, undefined, input.userContext),
phoneNumber: userInfo?.emails[0],
},
});
},
};
},
},
})
]
})

Note that we also save the phone number belonging to this user in the session. This will be used later on in the second login challenge

Next, we protect your APIs by overriding the getGlobalClaimValidators function in the Session recipe and adding a validator for PhoneVerifiedClaim. This will tell SuperTokens to check that this claim has the value of true whenever verifySession is called. If the value is not true, then verifySession will send a 403 status code to the frontend:

import EmailPassword from "supertokens-node/recipe/emailpassword";
import Session from "supertokens-node/recipe/session";
import supertokens from "supertokens-node";

supertokens.init({
framework: "...",
appInfo: { /*...*/ },
recipeList: [
EmailPassword.init({ /* ... */}),
Session.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
getGlobalClaimValidators: (input) => [
...input.claimValidatorsAddedByOtherRecipes,
PhoneVerifiedClaim.validators.hasValue(true),
],
// overrides from other steps...
};
},
},
})
]
})

You can disable this check for a specific routes by providing the overrideGlobalClaimValidators option when calling the verifySession function:

import { verifySession } from "supertokens-node/recipe/session/framework/express";
import express from "express";
import { SessionRequest } from "supertokens-node/framework/express";
const app = express();

/* ... */

app.get(
"/someAPI",
verifySession({
overrideGlobalClaimValidators: (globalValidators) => globalValidators.filter(v => v.id !== PhoneVerifiedClaim.key)
}),
async (req: SessionRequest, res) => {
let session = req.session!;

/* TODO: Your API logic... */
}
);
Looking for older versions of the documentation?
Which UI do you use?
Custom UI
Pre built UI