Protecting API routes
caution
- SuperTokens is not yet optimised for 2FA implementation, so you have to add a lot of customisations for it to work. We are working on improving the development experience for 2FA as well as adding more factors like TOPT. Stay tuned.
 - A demo app that uses the pre built UI can be found on our GitHub.
 
In the previous steps, we saw the a session is created after the first factor, with SecondFactorClaim set to false, and then after the second factor is completed, we update that value to true.
Protecting all APIs#
We want to protect all the application APIs such that they are accessible only when SecondFactorClaim is true - indicating that the user has completed 2FA. We can do this by by overriding the getGlobalClaimValidators function in the Session recipe.
- NodeJS
 - GoLang
 - Python
 
import Session from "supertokens-node/recipe/session";
Session.init({    override: {        functions: (oI) => {            return {                ...oI,                getGlobalClaimValidators: (input) => [                    ...input.claimValidatorsAddedByOtherRecipes,                    SecondFactorClaim.validators.hasValue(true),                ],            };        },    }})
import (    "github.com/supertokens/supertokens-golang/recipe/session"    "github.com/supertokens/supertokens-golang/recipe/session/claims"    "github.com/supertokens/supertokens-golang/recipe/session/sessmodels"    "github.com/supertokens/supertokens-golang/supertokens")
func main() {
    _, SecondFactorClaimValidator := claims.BooleanClaim("2fa-completed", func(userId string, userContext supertokens.UserContext) (interface{}, error) {        return false, nil    }, nil)
    session.Init(&sessmodels.TypeInput{        Override: &sessmodels.OverrideStruct{            Functions: func(originalImplementation sessmodels.RecipeInterface) sessmodels.RecipeInterface {                                (*originalImplementation.GetGlobalClaimValidators) = func(userId string, claimValidatorsAddedByOtherRecipes []claims.SessionClaimValidator, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {                    claimValidatorsAddedByOtherRecipes = append(claimValidatorsAddedByOtherRecipes,                        SecondFactorClaimValidator.HasValue(true, nil, nil))                    return claimValidatorsAddedByOtherRecipes, nil                }
                return originalImplementation            },        },    })}from typing import List, Dict, Anyfrom supertokens_python.recipe.session.claims import BooleanClaimfrom supertokens_python.recipe import sessionfrom supertokens_python.recipe.session.interfaces import RecipeInterface, SessionClaimValidator
SecondFactorClaim = BooleanClaim(    key="2fa-completed", fetch_value=lambda _, __: False)
def override_session_functions(original_implementation: RecipeInterface):
    async def get_global_claim_validators(        user_id: str,        claim_validators_added_by_other_recipes: List[SessionClaimValidator],        user_context: Dict[str, Any],    ):        return claim_validators_added_by_other_recipes + [SecondFactorClaim.validators.has_value(True)]
    original_implementation.get_global_claim_validators = get_global_claim_validators    return original_implementation
session.init(override=session.InputOverrideConfig(override_session_functions))Protecting specific API routes#
If instead, you want to enforce 2FA just on certain API routes, you can add the validator only when calling the verifySession function:
- 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";import { SessionRequest } from "supertokens-node/framework/express";
let app = express();
app.post("/like-comment", verifySession({    overrideGlobalClaimValidators: (globalValidators) => [        ...globalValidators,         SecondFactorClaim.validators.hasValue(true),    ]}), (req: SessionRequest, res) => {    //....});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: "/like-comment",    method: "post",    options: {        pre: [            {                method: verifySession({                    overrideGlobalClaimValidators: (globalValidators) => [                        ...globalValidators,                         SecondFactorClaim.validators.hasValue(true),                    ]                })            },        ],    },    handler: async (req: SessionRequest, res) => {        //...    }})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("/like-comment", {    preHandler: verifySession({        overrideGlobalClaimValidators: (globalValidators) => [            ...globalValidators,             SecondFactorClaim.validators.hasValue(true),        ]    }),}, (req: SessionRequest, res) => {    //....});import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";import { SessionEventV2 } from "supertokens-node/framework/awsLambda";
async function likeComment(awsEvent: SessionEventV2) {    //....};
exports.handler = verifySession(likeComment, {    overrideGlobalClaimValidators: (globalValidators) => [        ...globalValidators,         SecondFactorClaim.validators.hasValue(true),    ]});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("/like-comment", verifySession({    overrideGlobalClaimValidators: (globalValidators) => [        ...globalValidators,         SecondFactorClaim.validators.hasValue(true),    ]}), (ctx: SessionContext, next) => {    //....});import { inject, intercept } from "@loopback/core";import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest";import { verifySession } from "supertokens-node/recipe/session/framework/loopback";import { SessionContext } from "supertokens-node/framework/loopback";
class LikeComment {    constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { }    @post("/like-comment")    @intercept(verifySession({        overrideGlobalClaimValidators: (globalValidators) => [            ...globalValidators,             SecondFactorClaim.validators.hasValue(true),        ]    }))    @response(200)    handler() {        //....    }}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 likeComment(req: SessionRequest, res: any) {    await superTokensNextWrapper(        async (next) => {            await verifySession({                overrideGlobalClaimValidators: (globalValidators) => [                    ...globalValidators,                     SecondFactorClaim.validators.hasValue(true),                ]            })(req, res, next);        },        req,        res    )    //....}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')  // For more information about this guard please read our NestJS guide.  @UseGuards(new AuthGuard({      overrideGlobalClaimValidators: (globalValidators) => [        ...globalValidators,        SecondFactorClaim.validators.hasValue(true),      ]  }))   async postExample(@Session() session: SessionContainer): Promise<boolean> {    return true;  }}- Chi
 - net/http
 - Gin
 - Mux
 
import (    "net/http"
    "github.com/supertokens/supertokens-golang/recipe/session"    "github.com/supertokens/supertokens-golang/recipe/session/claims"    "github.com/supertokens/supertokens-golang/recipe/session/sessmodels"    "github.com/supertokens/supertokens-golang/supertokens")
func main() {    _, SecondFactorClaimValidator := claims.BooleanClaim("2fa-completed", func(userId string, userContext supertokens.UserContext) (interface{}, error) {        return false, nil    }, nil)
    http.ListenAndServe("SERVER ADDRESS", corsMiddleware(        supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {            // Handle your APIs..            if r.URL.Path == "/like-comment" {
                session.VerifySession(&sessmodels.VerifySessionOptions{                    OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {                        globalClaimValidators = append(globalClaimValidators,                            SecondFactorClaimValidator.HasValue(true, nil, nil))                        return globalClaimValidators, nil                    },                }, likeCommentAPI).ServeHTTP(rw, r)                return            }        }))))}
func corsMiddleware(next http.Handler) http.Handler {    return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) {        //...    })}
func likeCommentAPI(w http.ResponseWriter, r *http.Request) {    // If it comes here, the user has completed 2fa.}import (    "net/http"
    "github.com/gin-gonic/gin"    "github.com/supertokens/supertokens-golang/recipe/session"    "github.com/supertokens/supertokens-golang/recipe/session/claims"    "github.com/supertokens/supertokens-golang/recipe/session/sessmodels"    "github.com/supertokens/supertokens-golang/supertokens")
func main() {    _, SecondFactorClaimValidator := claims.BooleanClaim("2fa-completed", func(userId string, userContext supertokens.UserContext) (interface{}, error) {        return false, nil    }, nil)
    router := gin.New()
    router.GET("/like-comment", verifySession(&sessmodels.VerifySessionOptions{        OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {            globalClaimValidators = append(globalClaimValidators,                SecondFactorClaimValidator.HasValue(true, nil, nil))            return globalClaimValidators, nil        },    }), likeComment)}
// Wrap session.VerifySession to work with Ginfunc verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc {    return func(c *gin.Context) {        session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) {            c.Request = c.Request.WithContext(r.Context())            c.Next()        })(c.Writer, c.Request)        // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly        c.Abort()    }}
func likeComment(c *gin.Context) {    // If it comes here, the user has completed 2fa.}import (    "net/http"
    "github.com/go-chi/chi"    "github.com/supertokens/supertokens-golang/recipe/session"    "github.com/supertokens/supertokens-golang/recipe/session/claims"    "github.com/supertokens/supertokens-golang/recipe/session/sessmodels"    "github.com/supertokens/supertokens-golang/supertokens")
func main() {    _, SecondFactorClaimValidator := claims.BooleanClaim("2fa-completed", func(userId string, userContext supertokens.UserContext) (interface{}, error) {        return false, nil    }, nil)
    r := chi.NewRouter()
    r.Get("/like-comment", session.VerifySession(&sessmodels.VerifySessionOptions{        OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {            globalClaimValidators = append(globalClaimValidators,                SecondFactorClaimValidator.HasValue(true, nil, nil))            return globalClaimValidators, nil        },    }, likeComment))}
func likeComment(w http.ResponseWriter, r *http.Request) {    // If it comes here, the user has completed 2fa.}import (    "net/http"
    "github.com/gorilla/mux"    "github.com/supertokens/supertokens-golang/recipe/session"    "github.com/supertokens/supertokens-golang/recipe/session/claims"    "github.com/supertokens/supertokens-golang/recipe/session/sessmodels"    "github.com/supertokens/supertokens-golang/supertokens")
func main() {    _, SecondFactorClaimValidator := claims.BooleanClaim("2fa-completed", func(userId string, userContext supertokens.UserContext) (interface{}, error) {        return false, nil    }, nil)    router := mux.NewRouter()
    router.HandleFunc("/like-comment",        session.VerifySession(&sessmodels.VerifySessionOptions{            OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {                globalClaimValidators = append(globalClaimValidators,                    SecondFactorClaimValidator.HasValue(true, nil, nil))                return globalClaimValidators, nil            },        }, likeComment)).Methods(http.MethodGet)}
func likeComment(w http.ResponseWriter, r *http.Request) {    // If it comes here, the user has completed 2fa.}- FastAPI
 - Flask
 - Django
 
from supertokens_python.recipe.session.framework.fastapi import verify_sessionfrom supertokens_python.recipe.session import SessionContainerfrom fastapi import Dependsfrom supertokens_python.recipe.session.claims import BooleanClaim
SecondFactorClaim = BooleanClaim(    key="2fa-completed", fetch_value=lambda _, __: False)
@app.post('/like_comment')  async def like_comment(session: SessionContainer = Depends(        verify_session(            # We add the SecondFactorClaim's has_value(True) validator            override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \            [SecondFactorClaim.validators.has_value(True)]        ))):    # All validator checks have passed and the user has completed 2FA    passfrom supertokens_python.recipe.session.framework.flask import verify_sessionfrom supertokens_python.recipe.session.claims import BooleanClaim
SecondFactorClaim = BooleanClaim(    key="2fa-completed", fetch_value=lambda _, __: False)
@app.route('/update-jwt', methods=['POST'])  @verify_session(    # We add the SecondFactorClaim's has_value(True) validator    override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \    [SecondFactorClaim.validators.has_value(True)])def like_comment():    # All validator checks have passed and the user has completed 2FA    passfrom supertokens_python.recipe.session.framework.django.asyncio import verify_sessionfrom django.http import HttpRequestfrom supertokens_python.recipe.session.claims import BooleanClaim
SecondFactorClaim = BooleanClaim(    key="2fa-completed", fetch_value=lambda _, __: False)
@verify_session(    # We add the SecondFactorClaim's has_value(True) validator    override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \    [SecondFactorClaim.validators.has_value(True)])async def like_comment(request: HttpRequest):    # All validator checks have passed and the user has completed 2FA    passimportant
If the SecondFactorClaim claim validator fails, then the SDK will send a 403 response.