Protecting backend APIs and website routes
#
Protecting backend API routesCAUTION
This information only applies to scenarios in which you are using SuperTokens Session Access Tokens.
If you are implementing Unified Login you will have to manually check the email_verified
claim on the OAuth2 Access Tokens. Please read the separate page that shows you how to verify the token.
#
Add email verification checks to all API routesIf you want to protect all your backend API routes with email verification checks, set the mode
to REQUIRED
in the EmailVerification
config. Routes protected with the verifySession
middleware will now additionally check for email verification status.
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
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:
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js (Pages Dir)
- Next.js (App Dir)
- NestJS
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) => {
// 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.filter(v => v.id !== EmailVerificationClaim.key),
}),
},
],
},
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.filter(v => v.id !== EmailVerificationClaim.key),
}),
}, 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.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) => {
// 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.filter(v => v.id !== EmailVerificationClaim.key)
}))
@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.filter(v => v.id !== EmailVerificationClaim.key)
})(req, res, next);
},
req,
res
)
// All validator checks have passed and the user has a verified email address
}
import 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)
}
);
}
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> {
// All validator checks have passed and the user has a verified email address
return true;
}
}
- Chi
- net/http
- Gin
- Mux
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.
}
- FastAPI
- Flask
- Django
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]
)
)):
# 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 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():
# 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 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):
# All validator checks have passed and the user has a verified email address
pass
#
Add email verification checks to specific API routesIf you want to protect specific backend API routes with email verification checks, set the mode
to OPTIONAL
in the EmailVerification
config. You will then override the verifySession
middleware protecting the route to check for email verification status.
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js (Pages Dir)
- Next.js (App Dir)
- NestJS
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 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()]
}
);
}
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;
}
}
- Chi
- net/http
- Gin
- Mux
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.
}
- FastAPI
- Flask
- Django
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
#
Protecting frontend routes- ReactJS
- Angular
- Vue
#
Protect all frontend routesSet the email verification mode to REQUIRED
and wrap your website routes using <SessionAuth />
. If the user's email is not verified, SuperTokens will automatically redirect the user to the email verification screen.
#
Protect specific frontend routesSet the email verification mode to OPTIONAL
. We will create a generic component called VerifiedRoute
which enforces that its child components can only be rendered 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<any>) => {
return (
<SessionAuth>
<InvalidClaimHandler>
{props.children}
</InvalidClaimHandler>
</SessionAuth>
);
}
function InvalidClaimHandler(props: React.PropsWithChildren<any>) {
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>;
}
In the VerifiedRoute
component, we use the SessionAuth
wrapper to ensure that the session exists. The EmailVerificationClaim
validator is automatically added to the <SessionAuth>
component if the EmailVerification
recipe has been initialized.
Finally, we check the result of the validation in the InvalidClaimHandler
component which 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.
If all validation passes, we render the props.children
component.
note
You can extend the VerifiedRoute
component to check for other types of validators as well. This component can then be reused to protect all of your app's components (In this case, you may want to rename this component to something more appropriate, like ProtectedRoute
).
Manually checking verification status
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, and check it yourself:
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(): 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 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 will be reflected in the validationErrors variable. The EmailVerificationClaim validator will be automatically checked by this function since you have initialized the email verification recipe.
Checking validation error
In case the validationErrors
array is not empty, you can loop through the errors to know which claim has failed:
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)
}
}
}
Manually checking verification status
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, and 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
}
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 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 will be reflected in the validationErrors variable. The EmailVerificationClaim validator will be automatically checked by this function since you have initialized the email verification recipe.
Checking validation error
In case the validationErrors
array is not empty, you can loop through the errors to know which claim has failed:
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)
}
}
}
Manually checking verification status
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, and 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
}