Skip to content
Esc
navigateopen⌘Jpreview
Dashboard
On this page

Protect frontend and backend routes

Protect API and website routes by implementing email verification checks for secure access.

Overview

The EmailVerification claim shows the status of the email verification process. Follow this page to understand how to limit access based on whether the user has confirmed their email address.

Before you start


Protect backend routes

Add email verification checks on all routes

If you want to protect all your backend API routes with email verification checks, set the mode to REQUIRED in the EmailVerification configuration. Routes protected with the verifySession middleware additionally check for email verification status.

import SuperTokens from "supertokens-node";
import EmailVerification from "supertokens-node/recipe/emailverification";
import Session from "supertokens-node/recipe/session";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [
    EmailVerification.init({
      // This means that verifySession will now only allow calls if the user has verified their email
      mode: "REQUIRED",
    }),
    Session.init(),
  ],
});
import (
	"github.com/supertokens/supertokens-golang/recipe/emailverification"
	"github.com/supertokens/supertokens-golang/recipe/emailverification/evmodels"
	"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{
			emailverification.Init(evmodels.TypeInput{
                // This means that VerifySession will now only allow calls if the user has verified their email
				Mode: evmodels.ModeRequired,
			}),
			session.Init(&sessmodels.TypeInput{}),
		},
	})
}
from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import session
from supertokens_python.recipe import emailverification

init(
    app_info=InputAppInfo(
        api_domain="...", app_name="...", website_domain="..."),
    framework='...',  
    recipe_list=[
        # This means that VerifySession will now only allow calls if the user has verified their email
        emailverification.init(mode='REQUIRED'),
        session.init()
    ]
)

In case you have set the email verification mode to REQUIRED but want to disable the check for a specific route, you can make the following changes to the verifySession middleware:

Next.js router
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import express from "express";
import { SessionRequest } from "supertokens-node/framework/express";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

let app = express();

app.post(
  "/update-blog",
  verifySession({
    overrideGlobalClaimValidators: async (globalValidators) =>
      globalValidators.filter((v) => v.id !== EmailVerificationClaim.key),
  }),
  async (req: SessionRequest, res) => {
    // The session and remaining claim validators have passed; email verification was skipped
  },
);
import Hapi from "@hapi/hapi";
import { verifySession } from "supertokens-node/recipe/session/framework/hapi";
import { SessionRequest } from "supertokens-node/framework/hapi";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

let server = Hapi.server({ port: 8000 });

server.route({
  path: "/update-blog",
  method: "post",
  options: {
    pre: [
      {
        method: verifySession({
          overrideGlobalClaimValidators: async (globalValidators) =>
            globalValidators.filter((v) => v.id !== EmailVerificationClaim.key),
        }),
      },
    ],
  },
  handler: async (req: SessionRequest, res) => {
    // The session and remaining claim validators have passed; email verification was skipped
  },
});
import Fastify from "fastify";
import { verifySession } from "supertokens-node/recipe/session/framework/fastify";
import { SessionRequest } from "supertokens-node/framework/fastify";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

let fastify = Fastify();

fastify.post(
  "/update-blog",
  {
    preHandler: verifySession({
      overrideGlobalClaimValidators: async (globalValidators) =>
        globalValidators.filter((v) => v.id !== EmailVerificationClaim.key),
    }),
  },
  async (req: SessionRequest, res) => {
    // The session and remaining claim validators have passed; email verification was skipped
  },
);
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";
import { SessionEvent } from "supertokens-node/framework/awsLambda";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

async function updateBlog(awsEvent: SessionEvent) {
  // The session and remaining claim validators have passed; email verification was skipped
}

exports.handler = verifySession(updateBlog, {
  overrideGlobalClaimValidators: async (globalValidators) =>
    globalValidators.filter((v) => v.id !== EmailVerificationClaim.key),
});
import KoaRouter from "koa-router";
import { verifySession } from "supertokens-node/recipe/session/framework/koa";
import { SessionContext } from "supertokens-node/framework/koa";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

let router = new KoaRouter();

router.post(
  "/update-blog",
  verifySession({
    overrideGlobalClaimValidators: async (globalValidators) =>
      globalValidators.filter((v) => v.id !== EmailVerificationClaim.key),
  }),
  async (ctx: SessionContext, next) => {
    // The session and remaining claim validators have passed; email verification was skipped
  },
);
import { inject, intercept } from "@loopback/core";
import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest";
import { verifySession } from "supertokens-node/recipe/session/framework/loopback";
import Session from "supertokens-node/recipe/session";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

class SetRole {
  constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) {}
  @post("/update-blog")
  @intercept(
    verifySession({
      overrideGlobalClaimValidators: async (globalValidators) =>
        globalValidators.filter((v) => v.id !== EmailVerificationClaim.key),
    }),
  )
  @response(200)
  async handler() {
    // The session and remaining claim validators have passed; email verification was skipped
  }
}
import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common";
import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session";
import { AuthGuard } from "./auth/auth.guard";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

@Controller()
export class ExampleController {
  @Post("example")
  @UseGuards(
    new AuthGuard({
      overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) =>
        globalValidators.filter((v) => v.id !== EmailVerificationClaim.key),
    }),
  )
  async postExample(@Session() session: SessionContainer): Promise<boolean> {
    // The session and remaining claim validators have passed; email verification was skipped
    return true;
  }
}
import (
	"net/http"

	"github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims"
	"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() {
	_ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		session.VerifySession(&sessmodels.VerifySessionOptions{
			OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {
				filtered := []claims.SessionClaimValidator{}
				for _, v := range globalClaimValidators {
					// we keep all claim validators except for
					// the email verification claim validator.
					if v.ID != evclaims.EmailVerificationClaim.Key {
						filtered = append(filtered, v)
					}
				}
				return filtered, nil
			},
		}, exampleAPI).ServeHTTP(rw, r)
	})
}

func exampleAPI(w http.ResponseWriter, r *http.Request) {
	// TODO: session is verified and all validators have passed..
}
import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims"
	"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() {
	router := gin.New()

	// Wrap the API handler in session.VerifySession
	router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{
		OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {
			filtered := []claims.SessionClaimValidator{}
			for _, v := range globalClaimValidators {
				// we keep all claim validators except for
				// the email verification claim validator.
				if v.ID != evclaims.EmailVerificationClaim.Key {
					filtered = append(filtered, v)
				}
			}
			return filtered, nil
		},
	}), exampleAPI)
}

// This is a function that wraps the supertokens verification function
// to work the gin
func 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 exampleAPI(c *gin.Context) {
	// TODO: session is verified and all claim validators pass.
}
import (
	"net/http"

	"github.com/go-chi/chi"
	"github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims"
	"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() {
	r := chi.NewRouter()

	// Wrap the API handler in session.VerifySession
	r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{
		OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {
			filtered := []claims.SessionClaimValidator{}
			for _, v := range globalClaimValidators {
				// we keep all claim validators except for
				// the email verification claim validator.
				if v.ID != evclaims.EmailVerificationClaim.Key {
					filtered = append(filtered, v)
				}
			}
			return filtered, nil
		},
	}, exampleAPI))
}

func exampleAPI(w http.ResponseWriter, r *http.Request) {
	// TODO: session is verified and all claim validators pass.
}
import (
	"net/http"

	"github.com/gorilla/mux"
	"github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims"
	"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() {
	router := mux.NewRouter()

	// Wrap the API handler in session.VerifySession
	router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{
		OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {
			filtered := []claims.SessionClaimValidator{}
			for _, v := range globalClaimValidators {
				// we keep all claim validators except for
				// the email verification claim validator.
				if v.ID != evclaims.EmailVerificationClaim.Key {
					filtered = append(filtered, v)
				}
			}
			return filtered, nil
		},
	}, exampleAPI)).Methods(http.MethodPost)
}

func exampleAPI(w http.ResponseWriter, r *http.Request) {
	// TODO: session is verified and all claim validators pass.
}
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.emailverification import EmailVerificationClaim
from supertokens_python.recipe.session import SessionContainer
from fastapi import Depends

@app.post('/like_comment')  
async def like_comment(session: SessionContainer = Depends(
        verify_session(
            # We keep all validators except for the EmailVerification ones
            override_global_claim_validators=lambda global_validators, session, user_context: [
                validators for validators in global_validators if validators.id != EmailVerificationClaim.key]
        )
)):
    # The session and remaining claim validators have passed; email verification was skipped
    pass
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.emailverification import EmailVerificationClaim

@app.route('/update-jwt', methods=['POST'])  
@verify_session(
    # We keep all validators except for the EmailVerification ones
    override_global_claim_validators=lambda global_validators, session, user_context: [
        validators for validators in global_validators if validators.id != EmailVerificationClaim.key]
)
def like_comment():
    # The session and remaining claim validators have passed; email verification was skipped
    pass
from supertokens_python.recipe.session.framework.django.asyncio import verify_session
from django.http import HttpRequest
from supertokens_python.recipe.emailverification import EmailVerificationClaim

@verify_session(
    # We keep all validators except for the EmailVerification ones
    override_global_claim_validators=lambda global_validators, session, user_context: [
        validators for validators in global_validators if validators.id != EmailVerificationClaim.key]
)
async def like_comment(request: HttpRequest):
    # The session and remaining claim validators have passed; email verification was skipped
    pass

Add email verification checks to specific routes

If you want to protect specific backend API routes with email verification checks, set the mode to OPTIONAL in the EmailVerification configuration. You then override the verifySession middleware protecting the route to check for email verification status.

Next.js router
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import express from "express";
import { SessionRequest } from "supertokens-node/framework/express";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

let app = express();

app.post(
  "/update-blog",
  verifySession({
    overrideGlobalClaimValidators: async (globalValidators) => [
      ...globalValidators,
      EmailVerificationClaim.validators.isVerified(),
    ],
  }),
  async (req: SessionRequest, res) => {
    // All validator checks have passed and the user has a verified email address
  },
);
import Hapi from "@hapi/hapi";
import { verifySession } from "supertokens-node/recipe/session/framework/hapi";
import { SessionRequest } from "supertokens-node/framework/hapi";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

let server = Hapi.server({ port: 8000 });

server.route({
  path: "/update-blog",
  method: "post",
  options: {
    pre: [
      {
        method: verifySession({
          overrideGlobalClaimValidators: async (globalValidators) => [
            ...globalValidators,
            EmailVerificationClaim.validators.isVerified(),
          ],
        }),
      },
    ],
  },
  handler: async (req: SessionRequest, res) => {
    // All validator checks have passed and the user has a verified email address
  },
});
import Fastify from "fastify";
import { verifySession } from "supertokens-node/recipe/session/framework/fastify";
import { SessionRequest } from "supertokens-node/framework/fastify";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

let fastify = Fastify();

fastify.post(
  "/update-blog",
  {
    preHandler: verifySession({
      overrideGlobalClaimValidators: async (globalValidators) => [
        ...globalValidators,
        EmailVerificationClaim.validators.isVerified(),
      ],
    }),
  },
  async (req: SessionRequest, res) => {
    // All validator checks have passed and the user has a verified email address
  },
);
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";
import { SessionEvent } from "supertokens-node/framework/awsLambda";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

async function updateBlog(awsEvent: SessionEvent) {
  // All validator checks have passed and the user has a verified email address
}

exports.handler = verifySession(updateBlog, {
  overrideGlobalClaimValidators: async (globalValidators) => [
    ...globalValidators,
    EmailVerificationClaim.validators.isVerified(),
  ],
});
import KoaRouter from "koa-router";
import { verifySession } from "supertokens-node/recipe/session/framework/koa";
import { SessionContext } from "supertokens-node/framework/koa";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

let router = new KoaRouter();

router.post(
  "/update-blog",
  verifySession({
    overrideGlobalClaimValidators: async (globalValidators) => [
      ...globalValidators,
      EmailVerificationClaim.validators.isVerified(),
    ],
  }),
  async (ctx: SessionContext, next) => {
    // All validator checks have passed and the user has a verified email address
  },
);
import { inject, intercept } from "@loopback/core";
import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest";
import { verifySession } from "supertokens-node/recipe/session/framework/loopback";
import Session from "supertokens-node/recipe/session";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

class SetRole {
  constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) {}
  @post("/update-blog")
  @intercept(
    verifySession({
      overrideGlobalClaimValidators: async (globalValidators) => [
        ...globalValidators,
        EmailVerificationClaim.validators.isVerified(),
      ],
    }),
  )
  @response(200)
  async handler() {
    // All validator checks have passed and the user has a verified email address
  }
}
import { Controller, Post, UseGuards, Request, Response, Session } from "@nestjs/common";
import { SessionContainer, SessionClaimValidator } from "supertokens-node/recipe/session";
import { AuthGuard } from "./auth/auth.guard";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";

@Controller()
export class ExampleController {
  @Post("example")
  @UseGuards(
    new AuthGuard({
      overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => [
        ...globalValidators,
        EmailVerificationClaim.validators.isVerified(),
      ],
    }),
  )
  async postExample(@Session() session: SessionContainer): Promise<boolean> {
    // All validator checks have passed and the user has a verified email address
    return true;
  }
}
import (
	"net/http"

	"github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims"
	"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() {
	_ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		session.VerifySession(&sessmodels.VerifySessionOptions{
			OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {
				globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil))
				return globalClaimValidators, nil
			},
		}, exampleAPI).ServeHTTP(rw, r)
	})
}

func exampleAPI(w http.ResponseWriter, r *http.Request) {
	// TODO: session is verified and all validators have passed..
}
import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims"
	"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() {
	router := gin.New()

	// Wrap the API handler in session.VerifySession
	router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{
		OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {
			globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil))
			return globalClaimValidators, nil
		},
	}), exampleAPI)
}

// This is a function that wraps the supertokens verification function
// to work the gin
func 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 exampleAPI(c *gin.Context) {
	// TODO: session is verified and all claim validators pass.
}
import (
	"net/http"

	"github.com/go-chi/chi"
	"github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims"
	"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() {
	r := chi.NewRouter()

	// Wrap the API handler in session.VerifySession
	r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{
		OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {
			globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil))
			return globalClaimValidators, nil
		},
	}, exampleAPI))
}

func exampleAPI(w http.ResponseWriter, r *http.Request) {
	// TODO: session is verified and all claim validators pass.
}
import (
	"net/http"

	"github.com/gorilla/mux"
	"github.com/supertokens/supertokens-golang/recipe/emailverification/evclaims"
	"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() {
	router := mux.NewRouter()

	// Wrap the API handler in session.VerifySession
	router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{
		OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) {
			globalClaimValidators = append(globalClaimValidators, evclaims.EmailVerificationClaimValidators.IsVerified(nil, nil))
			return globalClaimValidators, nil
		},
	}, exampleAPI)).Methods(http.MethodPost)
}

func exampleAPI(w http.ResponseWriter, r *http.Request) {
	// TODO: session is verified and all claim validators pass.
}
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.emailverification import EmailVerificationClaim
from supertokens_python.recipe.session import SessionContainer
from fastapi import Depends

@app.post('/like_comment')  
async def like_comment(session: SessionContainer = Depends(
        verify_session(
            # We add the EmailVerificationClaim's is_verified validator
            override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \
            [EmailVerificationClaim.validators.is_verified()]
        )
)):
    # All validator checks have passed and the user has a verified email address
    pass
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.emailverification import EmailVerificationClaim

@app.route('/update-jwt', methods=['POST'])  
@verify_session(
    # We add the EmailVerificationClaim's is_verified validator
    override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \
    [EmailVerificationClaim.validators.is_verified()]
)
def like_comment():
    # All validator checks have passed and the user has a verified email address
    pass
from supertokens_python.recipe.session.framework.django.asyncio import verify_session
from django.http import HttpRequest
from supertokens_python.recipe.emailverification import EmailVerificationClaim

@verify_session(
    # We add the EmailVerificationClaim's is_verified validator
    override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \
    [EmailVerificationClaim.validators.is_verified()]
)
async def like_comment(request: HttpRequest):
    # All validator checks have passed and the user has a verified email address
    pass

Protect frontend routes

UI type

Protect all frontend routes

Set the email verification mode to REQUIRED and wrap your website routes using <SessionAuth />. If the user’s email is not verified, SuperTokens automatically redirects the user to the email verification screen.

Protect specific frontend routes

Set the email verification mode to OPTIONAL. Create a generic component called VerifiedRoute which enforces that its child components can only render if the user has a verified email address.

import React from "react";
import { SessionAuth, useSessionContext } from "supertokens-auth-react/recipe/session";
import { EmailVerificationClaim } from "supertokens-auth-react/recipe/emailverification";

const VerifiedRoute = (props: React.PropsWithChildren) => {
  return (
    <SessionAuth>
      <InvalidClaimHandler>{props.children}</InvalidClaimHandler>
    </SessionAuth>
  );
};

function InvalidClaimHandler(props: React.PropsWithChildren) {
  let sessionContext = useSessionContext();
  if (sessionContext.loading) {
    return null;
  }

  if (sessionContext.invalidClaims.some((i) => i.id === EmailVerificationClaim.id)) {
    // Alternatively you could redirect the user to the email verification screen to trigger the verification email
    // Note: /auth/verify-email is the default email verification path
    // window.location.assign("/auth/verify-email")
    return <div>You cannot access this page because your email address is not verified.</div>;
  }

  // We show the protected route since all claims validators have
  // passed implying that the user has verified their email.
  return <div>{props.children}</div>;
}
import Session from "supertokens-web-js/recipe/session";
import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification";

async function shouldLoadRoute(): Promise<boolean> {
  if (await Session.doesSessionExist()) {
    let validationErrors = await Session.validateClaims();

    if (validationErrors.length === 0) {
      // user has verified their email address
      return true;
    } else {
      for (const err of validationErrors) {
        if (err.id === EmailVerificationClaim.id) {
          // email is not verified
        }
      }
    }
  }
  // a session does not exist, or email is not verified
  return false;
}

In the VerifiedRoute component, use the SessionAuth wrapper to ensure that the session exists. The <SessionAuth> component automatically adds the EmailVerificationClaim validator if you initialize the EmailVerification recipe.

Finally, check the validation result in InvalidClaimHandler. It displays "You cannot access this page because your email address is not verified." if the EmailVerificationClaim validator failed. Alternatively you could also redirect the user to the default email verification path to trigger the sending of the verification email.

Check the verification status manually

If you want to have more complex access control, you can either create your own validator, or you can get the boolean from the session as follows. Check it yourself:

In your protected routes, you need to first check if a session exists, and then call the Session.validateClaims function as shown above. This function inspects the session’s contents and runs claim validators on them. If a claim validator fails, it reflects in the validationErrors variable. The EmailVerificationClaim validator is automatically checked by this function since you have initialized the email verification recipe.

Validation errors

In case the validationErrors array is not empty, you can loop through the errors to know which claim has failed:

Check the verification status manually

If you want to have more complex access control, you can either create your own validator, or you can get the boolean from the session as follows. Check it yourself:


See also

API reference

API schema and response details