Skip to content
Esc
↑↓navigate↵open⌘Jpreview
Dashboard
On this page

Protecting API routes

Protect API routes by enforcing 2FA using session claim validators in various programming languages.

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.

1. 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.

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, tenantId 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, tenantId string, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {
					claimValidatorsAddedByOtherRecipes = append(claimValidatorsAddedByOtherRecipes,
						SecondFactorClaimValidator.HasValue(true, nil, nil))
					return claimValidatorsAddedByOtherRecipes, nil
				}

				return originalImplementation
			},
		},
	})
}
from typing import List, Dict, Any
from supertokens_python.recipe.session.claims import BooleanClaim
from supertokens_python.recipe import session
from supertokens_python.recipe.session.interfaces import (
    RecipeInterface,
    SessionClaimValidator,
)
from supertokens_python.types import RecipeUserId

SecondFactorClaim = BooleanClaim(
    key="2fa-completed", fetch_value=lambda _, __, ___, ____, _____: False
)


def override_session_functions(original_implementation: RecipeInterface):

    async def get_global_claim_validators(
        tenant_id: str,
        user_id: str,
        recipe_user_id: RecipeUserId,
        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(functions=override_session_functions)
)

2. Protecting specific API routes

If instead, you want to enforce 2FA on certain API routes, you can add the validator only when calling the verifySession function:

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 (
	"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, tenantId 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.
}
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.session import SessionContainer
from fastapi import Depends
from 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
    pass

API reference

API schema and response details