Protecting API and frontend routes
#
Protecting API routesIn your API routes you:
- Verify that a session exists
- Validate that the roles/permissions saved in the access token payload have the appropriate value
- 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 UserRoles from "supertokens-node/recipe/userroles";
let app = express();
app.post(
"/update-blog",
verifySession({
overrideGlobalClaimValidators: async (globalValidators) => [
...globalValidators,
UserRoles.UserRoleClaim.validators.includes("admin"),
// UserRoles.PermissionClaim.validators.includes("edit")
],
}),
async (req: SessionRequest, res) => {
// All validator checks have passed and the user is an admin.
}
);
import Hapi from "@hapi/hapi";
import { verifySession } from "supertokens-node/recipe/session/framework/hapi";
import {SessionRequest} from "supertokens-node/framework/hapi";
import UserRoles from "supertokens-node/recipe/userroles";
let server = Hapi.server({ port: 8000 });
server.route({
path: "/update-blog",
method: "post",
options: {
pre: [
{
method: verifySession({
overrideGlobalClaimValidators: async (globalValidators) => [
...globalValidators,
UserRoles.UserRoleClaim.validators.includes("admin"),
// UserRoles.PermissionClaim.validators.includes("edit")
],
}),
},
],
},
handler: async (req: SessionRequest, res) => {
// All validator checks have passed and the user is an admin.
}
})
import Fastify from "fastify";
import { verifySession } from "supertokens-node/recipe/session/framework/fastify";
import { SessionRequest } from "supertokens-node/framework/fastify";
import UserRoles from "supertokens-node/recipe/userroles";
let fastify = Fastify();
fastify.post("/update-blog", {
preHandler: verifySession({
overrideGlobalClaimValidators: async (globalValidators) => [
...globalValidators,
UserRoles.UserRoleClaim.validators.includes("admin"),
// UserRoles.PermissionClaim.validators.includes("edit")
],
}),
}, async (req: SessionRequest, res) => {
// All validator checks have passed and the user is an admin.
});
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";
import { SessionEvent } from "supertokens-node/framework/awsLambda";
import UserRoles from "supertokens-node/recipe/userroles";
async function updateBlog(awsEvent: SessionEvent) {
// All validator checks have passed and the user is an admin.
};
exports.handler = verifySession(updateBlog, {
overrideGlobalClaimValidators: async (globalValidators) => ([
...globalValidators,
UserRoles.UserRoleClaim.validators.includes("admin"),
// UserRoles.PermissionClaim.validators.includes("edit")
])
});
import KoaRouter from "koa-router";
import { verifySession } from "supertokens-node/recipe/session/framework/koa";
import {SessionContext} from "supertokens-node/framework/koa";
import UserRoles from "supertokens-node/recipe/userroles";
let router = new KoaRouter();
router.post("/update-blog", verifySession({
overrideGlobalClaimValidators: async (globalValidators) => ([
...globalValidators,
UserRoles.UserRoleClaim.validators.includes("admin"),
// UserRoles.PermissionClaim.validators.includes("edit")
])
}), async (ctx: SessionContext, next) => {
// All validator checks have passed and the user is an admin.
});
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 UserRoles from "supertokens-node/recipe/userroles";
class SetRole {
constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { }
@post("/update-blog")
@intercept(verifySession({
overrideGlobalClaimValidators: async (globalValidators) => ([
...globalValidators,
UserRoles.UserRoleClaim.validators.includes("admin"),
// UserRoles.PermissionClaim.validators.includes("edit")
])
}))
@response(200)
async handler() {
// All validator checks have passed and the user is an admin.
}
}
import { superTokensNextWrapper } from 'supertokens-node/nextjs'
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";
import UserRoles from "supertokens-node/recipe/userroles";
export default async function setRole(req: SessionRequest, res: any) {
await superTokensNextWrapper(
async (next) => {
await verifySession({
overrideGlobalClaimValidators: async (globalValidators) => ([
...globalValidators,
UserRoles.UserRoleClaim.validators.includes("admin"),
// UserRoles.PermissionClaim.validators.includes("edit")
])
})(req, res, next);
},
req,
res
)
// All validator checks have passed and the user is an admin.
}
import { NextResponse, NextRequest } from "next/server";
import SuperTokens from "supertokens-node";
import { withSession } from "supertokens-node/nextjs";
import UserRoles from "supertokens-node/recipe/userroles";
import { backendConfig } from "@/app/config/backend";
SuperTokens.init(backendConfig());
export 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 is an admin.
return NextResponse.json({})
},
{
overrideGlobalClaimValidators: async function (globalClaimValidators) {
return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")]
}
});
}
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 UserRoles from "supertokens-node/recipe/userroles";
@Controller()
export class ExampleController {
@Post('example')
@UseGuards(new AuthGuard({
overrideGlobalClaimValidators: async (globalValidators: SessionClaimValidator[]) => ([
...globalValidators,
UserRoles.UserRoleClaim.validators.includes("admin"),
// UserRoles.PermissionClaim.validators.includes("edit")
])
}))
async postExample(@Session() session: SessionContainer): Promise<boolean> {
// All validator checks have passed and the user is an admin.
return true;
}
}
- Chi
- net/http
- Gin
- Mux
import (
"net/http"
"github.com/supertokens/supertokens-golang/recipe/userroles/userrolesclaims"
"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, userrolesclaims.UserRoleClaimValidators.Includes("admin", 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/userroles/userrolesclaims"
"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, userrolesclaims.UserRoleClaimValidators.Includes("admin", 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/userroles/userrolesclaims"
"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, userrolesclaims.UserRoleClaimValidators.Includes("admin", 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/userroles/userrolesclaims"
"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, userrolesclaims.UserRoleClaimValidators.Includes("admin", 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.userroles import UserRoleClaim
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 UserRoleClaim's includes validator
override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \
[UserRoleClaim.validators.includes("admin")]
)
)):
# 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.userroles import UserRoleClaim
@app.route('/update-jwt', methods=['POST'])
@verify_session(
# We add the UserRoleClaim's includes validator
override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \
[UserRoleClaim.validators.includes("admin")]
)
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.userroles import UserRoleClaim
@verify_session(
# We add the UserRoleClaim's includes validator
override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \
[UserRoleClaim.validators.includes("admin")]
)
async def like_comment(request: HttpRequest):
# All validator checks have passed and the user has a verified email address
pass
- We add the
UserRoleClaim
validator to theverifySession
function which makes sure that the user has anadmin
role. - The
globalValidators
represents other validators that apply to all API routes by default. This may include a validator that enforces that the user's email is verified (if enabled by you). - We can also add a
PermissionClaim
validator to enforce a permission.
If you want to have more complex access control, you can either create your own validator, or you can get the roles list from the session as follows, and check the list yourself:
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js (Pages Dir)
- Next.js (App Dir)
- NestJS
import express from "express";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";
import UserRoles from "supertokens-node/recipe/userroles";
import { Error as STError } from "supertokens-node/recipe/session"
let app = express();
app.post("/update-blog", verifySession(), async (req: SessionRequest, res) => {
const roles = await req.session!.getClaimValue(UserRoles.UserRoleClaim);
if (roles === undefined || !roles.includes("admin")) {
// this error tells SuperTokens to return a 403 to the frontend.
throw new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [{
id: UserRoles.UserRoleClaim.key
}]
})
}
// user is an admin..
});
import Hapi from "@hapi/hapi";
import { verifySession } from "supertokens-node/recipe/session/framework/hapi";
import {SessionRequest} from "supertokens-node/framework/hapi";
import UserRoles from "supertokens-node/recipe/userroles";
import { Error as STError } from "supertokens-node/recipe/session"
let server = Hapi.server({ port: 8000 });
server.route({
path: "/update-blog",
method: "post",
options: {
pre: [
{
method: verifySession()
},
],
},
handler: async (req: SessionRequest, res) => {
const roles = await req.session!.getClaimValue(UserRoles.UserRoleClaim);
if (roles === undefined || !roles.includes("admin")) {
// this error tells SuperTokens to return a 403 to the frontend.
throw new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [{
id: UserRoles.UserRoleClaim.key
}]
})
}
// user is an admin..
}
})
import Fastify from "fastify";
import { verifySession } from "supertokens-node/recipe/session/framework/fastify";
import { SessionRequest } from "supertokens-node/framework/fastify";
import UserRoles from "supertokens-node/recipe/userroles";
import { Error as STError } from "supertokens-node/recipe/session"
let fastify = Fastify();
fastify.post("/update-blog", {
preHandler: verifySession(),
}, async (req: SessionRequest, res) => {
const roles = await req.session!.getClaimValue(UserRoles.UserRoleClaim);
if (roles === undefined || !roles.includes("admin")) {
// this error tells SuperTokens to return a 403 to the frontend.
throw new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [{
id: UserRoles.UserRoleClaim.key
}]
})
}
// user is an admin..
});
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";
import { SessionEvent } from "supertokens-node/framework/awsLambda";
import UserRoles from "supertokens-node/recipe/userroles";
import { Error as STError } from "supertokens-node/recipe/session"
async function updateBlog(awsEvent: SessionEvent) {
const roles = await awsEvent.session!.getClaimValue(UserRoles.UserRoleClaim);
if (roles === undefined || !roles.includes("admin")) {
// this error tells SuperTokens to return a 403 to the frontend.
throw new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [{
id: UserRoles.UserRoleClaim.key
}]
})
}
// user is an admin..
};
exports.handler = verifySession(updateBlog);
import KoaRouter from "koa-router";
import { verifySession } from "supertokens-node/recipe/session/framework/koa";
import { SessionContext } from "supertokens-node/framework/koa";
import UserRoles from "supertokens-node/recipe/userroles";
import { Error as STError } from "supertokens-node/recipe/session"
let router = new KoaRouter();
router.post("/update-blog", verifySession(), async (ctx: SessionContext, next) => {
const roles = await ctx.session!.getClaimValue(UserRoles.UserRoleClaim);
if (roles === undefined || !roles.includes("admin")) {
// this error tells SuperTokens to return a 403 to the frontend.
throw new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [{
id: UserRoles.UserRoleClaim.key
}]
})
}
// user is an admin..
});
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 UserRoles from "supertokens-node/recipe/userroles";
import { Error as STError } from "supertokens-node/recipe/session"
class UpdateBlog {
constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) {}
@post("/update-blog")
@intercept(verifySession())
@response(200)
async handler() {
const roles = await ((this.ctx as any).session as Session.SessionContainer).getClaimValue(UserRoles.UserRoleClaim);
if (roles === undefined || !roles.includes("admin")) {
// this error tells SuperTokens to return a 403 to the frontend.
throw new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [{
id: UserRoles.UserRoleClaim.key
}]
})
}
// user is an admin..
}
}
import { superTokensNextWrapper } from 'supertokens-node/nextjs'
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";
import UserRoles from "supertokens-node/recipe/userroles";
import { Error as STError } from "supertokens-node/recipe/session"
export default async function updateBlog(req: SessionRequest, res: any) {
await superTokensNextWrapper(
async (next) => {
await verifySession()(req, res, next);
},
req,
res
)
const roles = await req.session!.getClaimValue(UserRoles.UserRoleClaim);
if (roles === undefined || !roles.includes("admin")) {
// this error tells SuperTokens to return a 403 to the frontend.
await superTokensNextWrapper(
async (next) => {
throw new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [{
id: UserRoles.UserRoleClaim.key
}]
})
},
req,
res
)
}
// user is an admin..
}
import { NextResponse, NextRequest } from "next/server";
import SuperTokens from "supertokens-node";
import { withSession } from "supertokens-node/nextjs";
import UserRoles from "supertokens-node/recipe/userroles";
import { backendConfig } from "@/app/config/backend";
import { Error as STError } from "supertokens-node/recipe/session"
SuperTokens.init(backendConfig());
export function POST(request: NextRequest) {
return withSession(request, async (err, session) => {
if (err) {
return NextResponse.json(err, { status: 500 });
}
const roles = await session!.getClaimValue(UserRoles.UserRoleClaim);
if (roles === undefined || !roles.includes("admin")) {
const error = new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [{
id: UserRoles.UserRoleClaim.key
}]
});
return NextResponse.json(error, { status: 403 });
}
// user is an admin..
return NextResponse.json({})
});
}
import { Controller, Post, UseGuards, Session } from "@nestjs/common";
import { SessionContainer } from "supertokens-node/recipe/session";
import { AuthGuard } from './auth/auth.guard';
import UserRoles from "supertokens-node/recipe/userroles";
import { Error as STError } from "supertokens-node/recipe/session"
@Controller()
export class ExampleController {
@Post('example')
@UseGuards(new AuthGuard())
async postExample(@Session() session: SessionContainer): Promise<boolean> {
const roles = await session.getClaimValue(UserRoles.UserRoleClaim);
if (roles === undefined || !roles.includes("admin")) {
// this error tells SuperTokens to return a 403 to the frontend.
throw new STError({
type: "INVALID_CLAIMS",
message: "User is not an admin",
payload: [{
id: UserRoles.UserRoleClaim.key
}]
})
}
// user is an admin..
return true;
}
}
- Chi
- net/http
- Gin
- Mux
import (
"net/http"
"github.com/supertokens/supertokens-golang/recipe/session"
"github.com/supertokens/supertokens-golang/recipe/session/claims"
sessionerror "github.com/supertokens/supertokens-golang/recipe/session/errors"
"github.com/supertokens/supertokens-golang/recipe/userroles/userrolesclaims"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
http.ListenAndServe("SERVER ADDRESS", corsMiddleware(
supertokens.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Handle your APIs..
if r.URL.Path == "/update-blog" && r.Method == "POST" {
// Calling the API with session verification
session.VerifySession(nil, postExample).ServeHTTP(rw, r)
return
}
}))))
}
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(response http.ResponseWriter, r *http.Request) {
//...
})
}
func postExample(w http.ResponseWriter, r *http.Request) {
sessionContainer := session.GetSessionFromRequestContext(r.Context())
roles := sessionContainer.GetClaimValue(userrolesclaims.UserRoleClaim)
if roles == nil || !contains(roles.([]interface{}), "admin") {
err := supertokens.ErrorHandler(sessionerror.InvalidClaimError{
Msg: "User is not an admin",
InvalidClaims: []claims.ClaimValidationError{
{ID: userrolesclaims.UserRoleClaim.Key},
},
}, r, w)
if err != nil {
// TODO: send 500 error to client
}
}
// User is an admin...
}
func contains(s []interface{}, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/supertokens/supertokens-golang/recipe/session"
"github.com/supertokens/supertokens-golang/recipe/session/claims"
sessionerror "github.com/supertokens/supertokens-golang/recipe/session/errors"
"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
"github.com/supertokens/supertokens-golang/recipe/userroles/userrolesclaims"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
router := gin.New()
router.POST("/update-blog", verifySession(nil), postExample)
}
// Wrap session.VerifySession to work with 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()
}
}
// This is the API handler.
func postExample(c *gin.Context) {
sessionContainer := session.GetSessionFromRequestContext(c.Request.Context())
roles := sessionContainer.GetClaimValue(userrolesclaims.UserRoleClaim)
if roles == nil || !contains(roles.([]interface{}), "admin") {
err := supertokens.ErrorHandler(sessionerror.InvalidClaimError{
Msg: "User is not an admin",
InvalidClaims: []claims.ClaimValidationError{
{ID: userrolesclaims.UserRoleClaim.Key},
},
}, c.Request, c.Writer)
if err != nil {
// TODO: send 500 error to client
}
}
// User is an admin...
}
func contains(s []interface{}, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
import (
"net/http"
"github.com/go-chi/chi"
"github.com/supertokens/supertokens-golang/recipe/session"
"github.com/supertokens/supertokens-golang/recipe/session/claims"
sessionerror "github.com/supertokens/supertokens-golang/recipe/session/errors"
"github.com/supertokens/supertokens-golang/recipe/userroles/userrolesclaims"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
r := chi.NewRouter()
r.Post("/update-blog", session.VerifySession(nil, postExample))
}
// This is the API handler.
func postExample(w http.ResponseWriter, r *http.Request) {
sessionContainer := session.GetSessionFromRequestContext(r.Context())
roles := sessionContainer.GetClaimValue(userrolesclaims.UserRoleClaim)
if roles == nil || !contains(roles.([]interface{}), "admin") {
err := supertokens.ErrorHandler(sessionerror.InvalidClaimError{
Msg: "User is not an admin",
InvalidClaims: []claims.ClaimValidationError{
{ID: userrolesclaims.UserRoleClaim.Key},
},
}, r, w)
if err != nil {
// TODO: send 500 error to client
}
}
}
func contains(s []interface{}, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
import (
"net/http"
"github.com/gorilla/mux"
"github.com/supertokens/supertokens-golang/recipe/session"
"github.com/supertokens/supertokens-golang/recipe/session/claims"
sessionerror "github.com/supertokens/supertokens-golang/recipe/session/errors"
"github.com/supertokens/supertokens-golang/recipe/userroles/userrolesclaims"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/update-blog",
session.VerifySession(nil, postExample)).Methods(http.MethodPost)
}
// This is the API handler.
func postExample(w http.ResponseWriter, r *http.Request) {
sessionContainer := session.GetSessionFromRequestContext(r.Context())
roles := sessionContainer.GetClaimValue(userrolesclaims.UserRoleClaim)
if roles == nil || !contains(roles.([]interface{}), "admin") {
err := supertokens.ErrorHandler(sessionerror.InvalidClaimError{
Msg: "User is not an admin",
InvalidClaims: []claims.ClaimValidationError{
{ID: userrolesclaims.UserRoleClaim.Key},
},
}, r, w)
if err != nil {
// TODO: send 500 error to client
}
}
}
func contains(s []interface{}, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
- FastAPI
- Flask
- Django
from fastapi import Depends
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.session.exceptions import raise_invalid_claims_exception, ClaimValidationError
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.userroles import UserRoleClaim
@app.post('/update-blog')
async def update_blog_api(session: SessionContainer = Depends(verify_session())):
roles = await session.get_claim_value(UserRoleClaim)
if roles is None or "admin" not in roles:
raise_invalid_claims_exception("User is not an admin", [
ClaimValidationError(UserRoleClaim.key, None)])
from flask import Flask, g
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.exceptions import raise_invalid_claims_exception, ClaimValidationError
from supertokens_python.recipe.userroles import UserRoleClaim
app = Flask(__name__)
@app.route('/update-blog', methods=['POST'])
@verify_session()
def set_role_api():
session: SessionContainer = g.supertokens
roles = session.sync_get_claim_value(UserRoleClaim)
if roles is None or "admin" not in roles:
raise_invalid_claims_exception("User is not an admin", [
ClaimValidationError(UserRoleClaim.key, None)])
from django.http import HttpRequest
from supertokens_python.recipe.session.framework.django.asyncio import verify_session
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.exceptions import raise_invalid_claims_exception, ClaimValidationError
from supertokens_python.recipe.userroles import UserRoleClaim
@verify_session()
async def get_user_info_api(request: HttpRequest):
session: SessionContainer = request.supertokens
roles = await session.get_claim_value(UserRoleClaim)
if roles is None or "admin" not in roles:
raise_invalid_claims_exception("User is not an admin", [
ClaimValidationError(UserRoleClaim.key, None)])
#
Protecting frontend routesOn your frontend:
- Verify that a session exists
- Use the roles / permissions claim validators to enforce certain roles and permissions.
- If the user doesn't have the right roles, we show them an error message indicating they don't have access.
- ReactJS
- Angular
- Vue
import Session from "supertokens-web-js/recipe/session";
import { UserRoleClaim, /*PermissionClaim*/ } from "supertokens-web-js/recipe/userroles";
async function shouldLoadRoute(): Promise<boolean> {
if (await Session.doesSessionExist()) {
let validationErrors = await Session.validateClaims({
overrideGlobalClaimValidators: (globalValidators) =>
[...globalValidators,
UserRoleClaim.validators.includes("admin"),
/* PermissionClaim.validators.includes("modify") */
]
});
if (validationErrors.length === 0) {
// user is an admin
return true;
}
for (const err of validationErrors) {
if (err.id === UserRoleClaim.id) {
// user roles claim check failed
} else {
// some other claim check failed (from the global validators list)
}
}
}
// either a session does not exist, or one of the validators failed.
// so we do not allow access to this page.
return false
}
- We call the
validateClaims
function with theUserRoleClaim
validator which makes sure that the user has anadmin
role. - The
globalValidators
represents other validators that apply to all calls to thevalidateClaims
function. This may include a validator that enforces that the user's email is verified (if enabled by you). - We can also add a
PermissionClaim
validator to enforce a permission.
If you want to have more complex access control, you can get the roles list from the session as follows, and check the list yourself:
import Session from "supertokens-web-js/recipe/session";
import { UserRoleClaim } from "supertokens-web-js/recipe/userroles";
async function shouldLoadRoute(): Promise<boolean> {
if (await Session.doesSessionExist()) {
let roles = await Session.getClaimValue({claim: UserRoleClaim});
if (Array.isArray(roles) && roles.includes("admin")) {
// User is an admin
return true;
}
}
// either a session does not exist, or the user is not an admin
return false
}
import React from "react";
import { SessionAuth } from 'supertokens-auth-react/recipe/session';
import { AccessDeniedScreen } from 'supertokens-auth-react/recipe/session/prebuiltui';
import { UserRoleClaim, /*PermissionClaim*/ } from 'supertokens-auth-react/recipe/userroles';
const AdminRoute = (props: React.PropsWithChildren<any>) => {
return (
<SessionAuth
accessDeniedScreen={AccessDeniedScreen}
overrideGlobalClaimValidators={(globalValidators) => [
...globalValidators, UserRoleClaim.validators.includes("admin"),
]
}>
{props.children}
</SessionAuth>
);
}
Above we are creating a generic component called AdminRoute
which enforces that its child components can only be rendered if the user has the admin role.
In the AdminRoute
component, we use the SessionAuth
wrapper to ensure that the session exists. We also add the UserRoleClaim
validator to the <SessionAuth>
component which checks if the validators pass or not. If all validation passes, we render the props.children
component. If the claim validation has failed, it will display the AccessDeniedScreen
component instead of rendering the children. You can also pass your own custom component to the accessDeniedScreen
prop.
note
You can extend the AdminRoute
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
).
If you want to have more complex access control, you can get the roles list from the session as follows, and check the list yourself:
import Session from "supertokens-auth-react/recipe/session";
import {UserRoleClaim} from "supertokens-auth-react/recipe/userroles"
function ProtectedComponent() {
let claimValue = Session.useClaimValue(UserRoleClaim)
if (claimValue.loading || !claimValue.doesSessionExist) {
return null;
}
let roles = claimValue.value;
if (Array.isArray(roles) && roles.includes("admin")) {
// User is an admin
} else {
// User doesn't have any roles, or is not an admin..
}
}
import Session from "supertokens-web-js/recipe/session";
import { UserRoleClaim, /*PermissionClaim*/ } from "supertokens-web-js/recipe/userroles";
async function shouldLoadRoute(): Promise<boolean> {
if (await Session.doesSessionExist()) {
let validationErrors = await Session.validateClaims({
overrideGlobalClaimValidators: (globalValidators) =>
[...globalValidators,
UserRoleClaim.validators.includes("admin"),
/* PermissionClaim.validators.includes("modify") */
]
});
if (validationErrors.length === 0) {
// user is an admin
return true;
}
for (const err of validationErrors) {
if (err.id === UserRoleClaim.id) {
// user roles claim check failed
} else {
// some other claim check failed (from the global validators list)
}
}
}
// either a session does not exist, or one of the validators failed.
// so we do not allow access to this page.
return false
}
- We call the
validateClaims
function with theUserRoleClaim
validator which makes sure that the user has anadmin
role. - The
globalValidators
represents other validators that apply to all calls to thevalidateClaims
function. This may include a validator that enforces that the user's email is verified (if enabled by you). - We can also add a
PermissionClaim
validator to enforce a permission.
If you want to have more complex access control, you can get the roles list from the session as follows, and check the list yourself:
import Session from "supertokens-web-js/recipe/session";
import { UserRoleClaim } from "supertokens-web-js/recipe/userroles";
async function shouldLoadRoute(): Promise<boolean> {
if (await Session.doesSessionExist()) {
let roles = await Session.getClaimValue({claim: UserRoleClaim});
if (Array.isArray(roles) && roles.includes("admin")) {
// User is an admin
return true;
}
}
// either a session does not exist, or the user is not an admin
return false
}