---
title: Protect frontend and backend routes
description: Protect frontend and backend routes using SuperTokens session access tokens and role-based validation.
sidebar:
  order: 5
---

## Overview

To limit access to your application resources based on roles and permissions you have to use the `UserRoleClaim` inside the session validation logic.

## Before you start

:::info[Access token guidance]

If you are implementing [**Unified Login**](/authentication/unified-login/introduction), which uses **OAuth2 Access Tokens**, please check the [separate page](/authentication/unified-login/verify-tokens) that shows you how to validate them.
You have to check for the `roles` claim in the token payload.

:::

---

## Protect backend routes

Override the global claim validators to integrate role verification in the standard flow.
The `GlobalValidators` represents other validators that apply to all API routes by default.
This may include a validator that enforces that the user has verified their email.

To perform the verification follow these steps:
- Add the `UserRoleClaim` validator to the `Verify Session` function which makes sure that the user has specific roles.
- Optionally, add a `PermissionClaim` validator to enforce a permission.


<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
<DependentContent passive group="node-frameworks">
<ContentOption title="Next.js" value="nextjs">
<NextjsRouterTypeSelect />
</ContentOption>
</DependentContent>
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
<DependentContent group="node-frameworks" label="Node.js framework">
<ContentOption title="Express" value="express">
```tsx
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.
  },
);
```
</ContentOption>
<ContentOption title="Hapi" value="hapi">
```tsx
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.
  },
});
```
</ContentOption>
<ContentOption title="Fastify" value="fastify">
```tsx
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.
  },
);
```
</ContentOption>
<ContentOption title="Aws Lambda" value="aws-lambda">
```tsx
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")
  ],
});
```
</ContentOption>
<ContentOption title="Koa" value="koa">
```tsx
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.
  },
);
```
</ContentOption>
<ContentOption title="LoopBack" value="loopback">
```tsx
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.
  }
}
```
</ContentOption>
<ContentOption title="Next.js" value="nextjs">
<ConditionalContent propertyName="nextjsRouterType" condition="pages-router">

```tsx
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.
}
```

</ConditionalContent>
</ContentOption>
<ContentOption title="Nestjs" value="nestjs">
```tsx check=false reason="application example imports local modules defined elsewhere"
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;
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Go" value="go">
<DependentContent group="go-frameworks" label="Go framework">
<ContentOption title="HTTP" value="http">
```go
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..
}
```
</ContentOption>
<ContentOption title="Gin" value="gin">
```go
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.
}
```
</ContentOption>
<ContentOption title="Chi" value="chi">
```go
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.
}

```
</ContentOption>
<ContentOption title="Mux" value="mux">
```go
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.
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Python" value="python">
<DependentContent group="python-frameworks" label="Python framework">
<ContentOption title="FastAPI" value="fastapi">
```python check=false reason="route fragment assumes an existing framework application"
from fastapi import Depends

from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.userroles import UserRoleClaim


@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
```
</ContentOption>
<ContentOption title="Flask" value="flask">
```python check=false reason="route fragment assumes an existing framework application"
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
```
</ContentOption>
<ContentOption title="Django" value="django">
```python
from django.http import HttpRequest

from supertokens_python.recipe.session.framework.django.asyncio import verify_session
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
```
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>

<CodeGroup passive group="backend-language">
<Tab title="Node.js" value="nodejs">
<DependentContent group="node-frameworks" label="Node.js framework">
<ContentOption title="Next.js" value="nextjs">
<ConditionalContent propertyName="nextjsRouterType" condition="app-router">

```tsx check=false reason="application example imports local modules defined elsewhere"
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")];
      },
    },
  );
}
```


</ConditionalContent>
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>


### Custom validation

If you want to have more complex access control you can get the list of roles attached to the session and introduce your own logic.

<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
<DependentContent passive group="node-frameworks">
<ContentOption title="Next.js" value="nextjs">
<NextjsRouterTypeSelect />
</ContentOption>
</DependentContent>
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
<DependentContent group="node-frameworks" label="Node.js framework">
<ContentOption title="Express" value="express">
```tsx
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..
});
```
</ContentOption>
<ContentOption title="Hapi" value="hapi">
```tsx
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..
  },
});
```
</ContentOption>
<ContentOption title="Fastify" value="fastify">
```tsx
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..
  },
);
```
</ContentOption>
<ContentOption title="Aws Lambda" value="aws-lambda">
```tsx
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);
```
</ContentOption>
<ContentOption title="Koa" value="koa">
```tsx
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..
});
```
</ContentOption>
<ContentOption title="LoopBack" value="loopback">
```tsx
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..
  }
}
```
</ContentOption>
<ContentOption title="Next.js" value="nextjs">
<ConditionalContent propertyName="nextjsRouterType" condition="pages-router">

```tsx
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..
}
```

</ConditionalContent>
</ContentOption>
<ContentOption title="Nestjs" value="nestjs">
```tsx check=false reason="application example imports local modules defined elsewhere"
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;
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Go" value="go">
<DependentContent group="go-frameworks" label="Go framework">
<ContentOption title="HTTP" value="http">
```go
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 {
			http.Error(w, "Internal server error", http.StatusInternalServerError)
		}
		return
	}

	// User is an admin...
}

func contains(s []interface{}, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}
```
</ContentOption>
<ContentOption title="Gin" value="gin">
```go
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 {
			http.Error(c.Writer, "Internal server error", http.StatusInternalServerError)
		}
		return
	}

	// User is an admin...
}

func contains(s []interface{}, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}
```
</ContentOption>
<ContentOption title="Chi" value="chi">
```go
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 {
			http.Error(w, "Internal server error", http.StatusInternalServerError)
		}
		return
	}
}

func contains(s []interface{}, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}
```
</ContentOption>
<ContentOption title="Mux" value="mux">
```go
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 {
			http.Error(w, "Internal server error", http.StatusInternalServerError)
		}
		return
	}
}

func contains(s []interface{}, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Python" value="python">
<DependentContent group="python-frameworks" label="Python framework">
<ContentOption title="FastAPI" value="fastapi">
```python check=false reason="route fragment assumes an existing framework application"
from fastapi import Depends

from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.exceptions import (
    ClaimValidationError,
    raise_invalid_claims_exception,
)
from supertokens_python.recipe.session.framework.fastapi import verify_session
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)]
        )

```
</ContentOption>
<ContentOption title="Flask" value="flask">
```python check=false reason="route fragment assumes an existing framework application"
from flask import Flask, g

from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.exceptions import (
    ClaimValidationError,
    raise_invalid_claims_exception,
)
from supertokens_python.recipe.session.framework.flask import verify_session
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)]
        )

```
</ContentOption>
<ContentOption title="Django" value="django">
```python check=false reason="initialization excerpt omits deployment connection config"
from typing import cast

from django.http import HttpRequest

from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.exceptions import (
    ClaimValidationError,
    raise_invalid_claims_exception,
)
from supertokens_python.recipe.session.framework.django.asyncio import verify_session
from supertokens_python.recipe.userroles import UserRoleClaim


@verify_session()
async def get_user_info_api(request: HttpRequest):
    session: SessionContainer = cast(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)]
        )
```
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>

<CodeGroup passive group="backend-language">
<Tab title="Node.js" value="nodejs">
<DependentContent group="node-frameworks" label="Node.js framework">
<ContentOption title="Next.js" value="nextjs">
<ConditionalContent propertyName="nextjsRouterType" condition="app-router">

```tsx check=false reason="application example imports local modules defined elsewhere"
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({});
  });
}
```

</ConditionalContent>
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>

---

## Protect frontend routes

:::caution[Backend authorization is mandatory]

Frontend role and permission checks are for user experience only; they are not an authorization boundary. Always
enforce authorization on the backend before allowing access to a protected resource or operation.

:::

On your frontend:

1. Verify that a session exists
2. Use the roles / permissions claim validators to enforce certain roles and permissions.
3. If the user doesn't have the right roles, the system shows an error message indicating they don't have access.

<UITypeSwitch />

<VariantContent storageKey="ui-type" value="prebuilt">

<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
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>
  );
};
```
</Tab>
<Tab title="Angular" value="angular">
```tsx
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;
}
```
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
Above, you create a generic component called `AdminRoute` which enforces that its child components render only if the user has the admin role.

In the `AdminRoute` component, the `SessionAuth` wrapper ensures that the session exists. The `UserRoleClaim` validator is also added to the `<SessionAuth>` component which checks if the validators pass or not. If all validation passes, the `props.children` component renders. If the claim validation has failed, it displays the `AccessDeniedScreen` component instead of rendering the children. You can also pass a 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 reuse to protect all 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:
</ContentOption>
<ContentOption title="Angular" value="angular">
- We call the `validateClaims` function with the `UserRoleClaim` validator which makes sure that the user has an `admin` role.
- The `globalValidators` represents other validators that apply to all calls to the `validateClaims` function. This may include a validator that enforces that the user has verified their email (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:
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
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..
  }
}
```
</Tab>
<Tab title="Angular" value="angular">
```tsx
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;
}
```
</Tab>
</CodeGroup>

</VariantContent>

<VariantContent storageKey="ui-type" value="custom">



<CodeGroup group="frontend-custom-ui">
<Tab title="Web" value="web">
<DependentContent group="install-method" label="Installation method">
<ContentOption title="npm" value="npm">
```tsx
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;
}
```
</ContentOption>
<ContentOption title="Script tag" value="script-tag">
```tsx check=false reason="script-tag example relies on globals provided by loaded SuperTokens bundles"
async function shouldLoadRoute(): Promise<boolean> {
  if (await supertokensSession.doesSessionExist()) {
    let validationErrors = await supertokensSession.validateClaims({
      overrideGlobalClaimValidators: (globalValidators) => [
        ...globalValidators,
        supertokensUserRoles.UserRoleClaim.validators.includes("admin"),
        /* supertokensUserRoles.PermissionClaim.validators.includes("modify") */
      ],
    });

    if (validationErrors.length === 0) {
      // user is an admin
      return true;
    }

    for (const err of validationErrors) {
      if (err.id === supertokensUserRoles.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;
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Mobile" value="mobile">
<DependentContent group="mobile-frameworks" label="Mobile framework">
<ContentOption title="ReactNative" value="reactnative">
```tsx
import SuperTokens from "supertokens-react-native";

async function getRole() {
  if (await SuperTokens.doesSessionExist()) {
    let roles: string[] = (await SuperTokens.getAccessTokenPayloadSecurely())["st-role"].v;

    if (roles.includes("admin")) {
      // TODO..
    } else {
      // TODO..
    }
  }
}
```
</ContentOption>
<ContentOption title="Android" value="android">
```kotlin
import android.app.Application
import com.supertokens.session.SuperTokens
import org.json.JSONObject

class MainApplication: Application() {
    fun checkIfUserIsAnAdmin() {
        val accessTokenPayload: JSONObject = SuperTokens.getAccessTokenPayloadSecurely(this);
        val roles: List<String> = (accessTokenPayload.get("st-role") as JSONObject).get("v") as List<String>;
        if (roles.contains("admin")) {
            // user is an admin
        } else {
            // user is not an admin
        }
    }
}
```
</ContentOption>
<ContentOption title="iOS" value="ios">
```swift
import UIKit
import SuperTokensIOS

fileprivate class ViewController: UIViewController {
    func checkIfUserIsAnAdmin() {
        if let accessTokenPayload: [String: Any] = try? SuperTokens.getAccessTokenPayloadSecurely(), let roleObject: [String: Any] = accessTokenPayload["st-role"] as? [String: Any], let roles: [String] = roleObject["v"] as? [String] {
            if roles.contains("admin") {
                // user is an admin
            } else {
                // user is not an admin
            }
        }
    }
}
```
</ContentOption>
<ContentOption title="Flutter" value="flutter">
```dart
import 'package:supertokens_flutter/supertokens.dart';

Future<void> checkIfUserIsAnAdmin() async {
    var accessTokenPayload = await SuperTokens.getAccessTokenPayloadSecurely();

    if (accessTokenPayload.containsKey("st-role")) {
      Map<String, dynamic> roleObject = accessTokenPayload["st-role"];

      if (roleObject.containsKey("v")) {
        List<String> roles = roleObject["v"];

        if (roles.contains("admin")) {
          // user is an admin
        } else {
          // user is not an admin
        }
      }
    }
}
```
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-custom-ui">
<ContentOption title="Web" value="web">
<DependentContent passive group="install-method">
<ContentOption title="npm" value="npm">
<div>


- We call the `validateClaims` function with the `UserRoleClaim` validator which makes sure that the user has an `admin` role.
- The `globalValidators` represents other validators that apply to all calls to the `validateClaims` function. This may include a validator that enforces that the user has verified their email (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:

</div>
</ContentOption>
<ContentOption title="Script tag" value="script-tag">
- We call the `validateClaims` function with the `UserRoleClaim` validator which makes sure that the user has an `admin` role.
- The `globalValidators` represents other validators that apply to all calls to the `validateClaims` function. This may include a validator that enforces that the user has verified their email (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:
</ContentOption>
</DependentContent>
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-custom-ui">
<Tab title="Web" value="web">
<DependentContent group="install-method" label="Installation method">
<ContentOption title="npm" value="npm">
```tsx
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 (roles !== undefined && roles.includes("admin")) {
      // User is an admin
      return true;
    }
  }
  // either a session does not exist, or the user is not an admin
  return false;
}
```
</ContentOption>
<ContentOption title="Script tag" value="script-tag">
```tsx check=false reason="script-tag example relies on globals provided by loaded SuperTokens bundles"
async function shouldLoadRoute(): Promise<boolean> {
  if (await supertokensSession.doesSessionExist()) {
    let roles = await supertokensSession.getClaimValue({ claim: supertokensUserRoles.UserRoleClaim });
    if (roles !== undefined && roles.includes("admin")) {
      // User is an admin
      return true;
    }
  }
  // either a session does not exist, or the user is not an admin
  return false;
}
```
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>



</VariantContent>

---

## See also

<CardGroup cols={3}>
  <Card title="Role management actions" href="/additional-verification/user-roles/role-management-actions" />
  <Card title="Claim validation" href="/additional-verification/session-verification/claim-validation" />
  <Card title="Protect backend routes" href="/additional-verification/session-verification/protect-api-routes" />
  <Card title="Protect frontend routes" href="/additional-verification/session-verification/protect-frontend-routes" />
</CardGroup>
