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:
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 { superTokensNextWrapper } from "supertokens-node/nextjs";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";
export default async function setRole(req: SessionRequest, res: any) {
await superTokensNextWrapper(
async (next) => {
await verifySession({
overrideGlobalClaimValidators: async (globalValidators) =>
globalValidators.filter((v) => v.id !== EmailVerificationClaim.key),
})(req, res, next);
},
req,
res,
);
// 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
passfrom 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
passfrom 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
passimport SuperTokens from "supertokens-node";
import { NextResponse, NextRequest } from "next/server";
import { withSession } from "supertokens-node/nextjs";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";
import { backendConfig } from "@/app/config/backend";
SuperTokens.init(backendConfig());
export async function POST(request: NextRequest) {
return withSession(
request,
async (err, session) => {
if (err) {
return NextResponse.json(err, { status: 500 });
}
// We skipped checking the email verification claim
return NextResponse.json({});
},
{
overrideGlobalClaimValidators: async (globalValidators) =>
globalValidators.filter((v) => v.id !== EmailVerificationClaim.key),
},
);
}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.
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 { superTokensNextWrapper } from "supertokens-node/nextjs";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";
export default async function setRole(req: SessionRequest, res: any) {
await superTokensNextWrapper(
async (next) => {
await verifySession({
overrideGlobalClaimValidators: async (globalValidators) => [
...globalValidators,
EmailVerificationClaim.validators.isVerified(),
],
})(req, res, next);
},
req,
res,
);
// 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
passfrom 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
passfrom 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
passimport SuperTokens from "supertokens-node";
import { NextResponse, NextRequest } from "next/server";
import { withSession } from "supertokens-node/nextjs";
import { EmailVerificationClaim } from "supertokens-node/recipe/emailverification";
import { backendConfig } from "@/app/config/backend";
SuperTokens.init(backendConfig());
export async function POST(request: NextRequest) {
return withSession(
request,
async (err, session) => {
if (err) {
return NextResponse.json(err, { status: 500 });
}
// All validator checks have passed and the user has a verified email address
return NextResponse.json({});
},
{
overrideGlobalClaimValidators: async (globalValidators) => [
...globalValidators,
EmailVerificationClaim.validators.isVerified(),
],
},
);
}Protect frontend routes
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:
import Session from "supertokens-auth-react/recipe/session";
import { EmailVerificationClaim } from "supertokens-auth-react/recipe/emailverification";
function ProtectedComponent() {
let claimValue = Session.useClaimValue(EmailVerificationClaim);
if (claimValue.loading || !claimValue.doesSessionExist) {
return null;
}
let isEmailVerified = claimValue.value;
if (isEmailVerified !== undefined && isEmailVerified) {
//...
} else {
// Redirect the user the email verification path to send the verification email
// Note: /auth/verify-email is the default email verification path
window.location.assign("/auth/verify-email");
}
}import Session from "supertokens-web-js/recipe/session";
import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification";
async function shouldLoadRoute() {
let validationErrors = await Session.validateClaims(/*{...}*/);
for (const err of validationErrors) {
if (err.id === EmailVerificationClaim.id) {
// email verification claim check failed
} else {
// some other claim check failed (from the global validators list)
}
}
}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:
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 isVerified = await Session.getClaimValue({ claim: EmailVerificationClaim });
if (isVerified) {
// user has verified their email address
return true;
}
}
// either a session does not exist, or the user has not verified their email address
return false;
}Protect frontend routes
import Session from "supertokens-web-js/recipe/session";
import { EmailVerificationClaim, sendVerificationEmail } 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
// Send the verification email to the user
await sendEmail();
}
}
}
}
// a session does not exist, or email is not verified
return false;
}
async function sendEmail() {
try {
let response = await sendVerificationEmail();
if (response.status === "EMAIL_ALREADY_VERIFIED_ERROR") {
// This can happen if the info about email verification in the session was outdated.
// Redirect the user to the home page
window.location.assign("/home");
} else {
// email was sent successfully.
window.alert("Please check your email and click the link in it");
}
} catch (err: any) {
if (err.isSuperTokensGeneralError === true) {
// this may be a custom error message sent from the API by you.
window.alert(err.message);
} else {
window.alert("Oops! Something went wrong.");
}
}
}import SuperTokens from "supertokens-react-native";
async function checkIfEmailIsVerified() {
if (await SuperTokens.doesSessionExist()) {
let isVerified: boolean = (await SuperTokens.getAccessTokenPayloadSecurely())["st-ev"].v;
if (isVerified) {
// TODO..
} else {
// You can trigger the sending of the verification email by calling `<YOUR_API_DOMAIN>/auth/user/email/verify/token`
}
}
}import android.app.Application
import com.supertokens.session.SuperTokens
import org.json.JSONObject
class MainApplication: Application() {
fun checkIfEmailIsVerified() {
val accessTokenPayload: JSONObject = SuperTokens.getAccessTokenPayloadSecurely(this);
val isVerified: Boolean = (accessTokenPayload.get("st-ev") as JSONObject).get("v") as Boolean
if (isVerified) {
// TODO..
} else {
// You can trigger the sending of the verification email by calling `<YOUR_API_DOMAIN>/auth/user/email/verify/token`
}
}
}import UIKit
import SuperTokensIOS
fileprivate class ViewController: UIViewController {
func checkIfEmailIsVerified() {
if let accessTokenPayload: [String: Any] = try? SuperTokens.getAccessTokenPayloadSecurely(), let emailVerificationObject: [String: Any] = accessTokenPayload["st-ev"] as? [String: Any], let isVerified: Bool = emailVerificationObject["v"] as? Bool {
if isVerified {
// Email is verified
} else {
// You can trigger the sending of the verification email by calling `<YOUR_API_DOMAIN>/auth/user/email/verify/token`
}
}
}
}import 'package:supertokens_flutter/supertokens.dart';
Future<void> checkIfEmailIsVerified() async {
var accessTokenPayload = await SuperTokens.getAccessTokenPayloadSecurely();
if (accessTokenPayload.containsKey("st-ev")) {
Map<String, dynamic> emailVerificationObject = accessTokenPayload["st-ev"];
if (emailVerificationObject.containsKey("v")) {
bool isVerified = emailVerificationObject["v"];
if (isVerified) {
// Email is verified
} else {
// You can trigger the sending of the verification email by calling `<YOUR_API_DOMAIN>/auth/user/email/verify/token`
}
}
}
}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.
Handle 403 responses on the frontend
If your frontend queries a protected API on your backend and it fails with a 403, you can call the validateClaims function. Loop through the errors to know which claim has failed:
Handle 403 responses on the frontend
If your frontend queries a protected API on your backend and it fails with a 403, you can check the value of the st-ev claim in the access token payload.
If it is false, you can send the verification email.
import axios from "axios";
import Session from "supertokens-web-js/recipe/session";
import { EmailVerificationClaim } from "supertokens-web-js/recipe/emailverification";
async function callProtectedRoute() {
try {
let response = await axios.get("<YOUR_API_DOMAIN>/protectedroute");
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 403) {
let validationErrors = await Session.validateClaims();
for (let err of validationErrors) {
if (err.id === EmailVerificationClaim.id) {
// email verification claim check failed
// We call the sendEmail function defined in the previous section to send the verification email.
// await sendEmail();
} else {
// some other claim check failed (from the global validators list)
}
}
}
}
}