---
title: Protect backend routes
description: "Protect backend routes with SuperTokens session middleware and session verification APIs."
sidebar:
  order: 1
---

## Overview

Use the `Verify Session` middleware when your framework supports middleware. Otherwise, call `Get Session` directly.
Both methods validate the complete SuperTokens session token and configured session claims. Manual JWT verification is a
fallback for platforms without a released SuperTokens backend SDK.


## Before you start

:::info[Access token guidance]
This guide applies to scenarios involving **SuperTokens Session Access Tokens**.
:::

---

## Using `Verify Session`

This function acts as a middleware inside your API endpoints.
Hence, it requires that your backend framework supports the concept of middlewares.
Besides checking for a session, it also writes responses to the client on its own, based on the session's validity and the provided configuration.

<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";

let app = express();

app.post("/like-comment", verifySession(), (req: SessionRequest, res) => {
  let userId = req.session!.getUserId();
  //....
});
```
</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";

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

server.route({
  path: "/like-comment",
  method: "post",
  options: {
    pre: [
      {
        method: verifySession(),
      },
    ],
  },
  handler: async (req: SessionRequest, res) => {
    let userId = req.session!.getUserId();
    //...
  },
});
```
</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";

let fastify = Fastify();

fastify.post(
  "/like-comment",
  {
    preHandler: verifySession(),
  },
  (req: SessionRequest, res) => {
    let userId = req.session!.getUserId();
    //....
  },
);
```
</ContentOption>
<ContentOption title="Aws Lambda" value="aws-lambda">
```tsx
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";
import { SessionEventV2 } from "supertokens-node/framework/awsLambda";

async function likeComment(awsEvent: SessionEventV2) {
  let userId = awsEvent.session!.getUserId();
  //....
}

exports.handler = verifySession(likeComment);
```
</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";

let router = new KoaRouter();

router.post("/like-comment", verifySession(), (ctx: SessionContext, next) => {
  let userId = ctx.session!.getUserId();
  //....
});
```
</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 { SessionContext } from "supertokens-node/framework/loopback";

class LikeComment {
  constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) {}
  @post("/like-comment")
  @intercept(verifySession())
  @response(200)
  handler() {
    let userId = (this.ctx as SessionContext).session!.getUserId();
    //....
  }
}
```
</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";

export default async function likeComment(req: SessionRequest, res: any) {
  await superTokensNextWrapper(
    async (next) => {
      await verifySession()(req, res, next);
    },
    req,
    res,
  );

  let userId = req.session!.getUserId();
  //....
}
```

</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";

@Controller()
export class ExampleController {
  @Post("example")
  @UseGuards(new AuthGuard()) // For more information about this guard please read our NestJS guide.
  async postExample(@Session() session: SessionContainer): Promise<boolean> {
    let userId = session.getUserId();

    //....
    return true;
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Go" value="go">
<DependentContent group="go-frameworks" label="Go framework">
<ContentOption title="HTTP" value="http">
```go
import (
	"fmt"
	"net/http"

	"github.com/supertokens/supertokens-golang/recipe/session"
)

func main() {
	_ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		// Wrap the API handler in session.VerifySession
		session.VerifySession(nil, likeCommentAPI).ServeHTTP(rw, r)
	})
}

func likeCommentAPI(w http.ResponseWriter, r *http.Request) {
	// retrieve the session object as shown below
	sessionContainer := session.GetSessionFromRequestContext(r.Context())

	userID := sessionContainer.GetUserID()

	fmt.Println(userID)
}
```
</ContentOption>
<ContentOption title="Gin" value="gin">
```go
import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
)

func main() {
	router := gin.New()

	// Wrap the API handler in session.VerifySession
	router.POST("/likecomment", verifySession(nil), likeCommentAPI)
}

// 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 likeCommentAPI(c *gin.Context) {
	// retrieve the session object as shown below
	sessionContainer := session.GetSessionFromRequestContext(c.Request.Context())

	userID := sessionContainer.GetUserID()

	fmt.Println(userID)
}
```
</ContentOption>
<ContentOption title="Chi" value="chi">
```go
import (
	"fmt"
	"net/http"

	"github.com/go-chi/chi"
	"github.com/supertokens/supertokens-golang/recipe/session"
)

func main() {
	r := chi.NewRouter()

	// Wrap the API handler in session.VerifySession
	r.Post("/likecomment", session.VerifySession(nil, likeCommentAPI))
}

func likeCommentAPI(w http.ResponseWriter, r *http.Request) {
	// retrieve the session object as shown below
	sessionContainer := session.GetSessionFromRequestContext(r.Context())

	userID := sessionContainer.GetUserID()

	fmt.Println(userID)
}
```
</ContentOption>
<ContentOption title="Mux" value="mux">
```go
import (
	"fmt"
	"net/http"

	"github.com/gorilla/mux"
	"github.com/supertokens/supertokens-golang/recipe/session"
)

func main() {
	router := mux.NewRouter()

	// Wrap the API handler in session.VerifySession
	router.HandleFunc("/likecomment", session.VerifySession(nil, likeCommentAPI)).Methods(http.MethodPost)
}

func likeCommentAPI(w http.ResponseWriter, r *http.Request) {
	// retrieve the session object as shown below
	sessionContainer := session.GetSessionFromRequestContext(r.Context())

	userID := sessionContainer.GetUserID()

	fmt.Println(userID)
}
```
</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


@app.post('/like_comment') 
async def like_comment(session: SessionContainer = Depends(verify_session())):
    user_id = session.get_user_id()

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

from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.framework.flask import verify_session


@app.route('/update-jwt', methods=['POST']) 
@verify_session()
def like_comment():
    session: SessionContainer = g.supertokens 

    user_id = session.get_user_id()

    print(user_id)
```
</ContentOption>
<ContentOption title="Django" value="django">
```python check=false reason="session attribute is injected by framework middleware"
from typing import cast

from django.http import HttpRequest

from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.framework.django.asyncio import verify_session


@verify_session()
async def like_comment(request: HttpRequest):
    session: SessionContainer = cast(SessionContainer, request.supertokens) 

    user_id = session.get_user_id()

    print(user_id)
```
</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 { 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 });
    }
    let userId = session!.getUserId();
    //....
    return NextResponse.json({});
  });
}
```

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


<Accordion>
<AccordionItem title="The Session object">
<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx check=false reason="public interface excerpt omits dependent SDK type declarations"
interface Session {
  /**
   * Destroys this session in the database and on the frontend.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the session is successfully revoked.
   */
  revokeSession(userContext?: Record<string, any>): Promise<void>;

  /**
   * Retrieves the session data stored in the database associated with the session.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the session data.
   */
  getSessionDataFromDatabase(userContext?: Record<string, any>): Promise<any>;

  /**
   * Sets a new JSON object to the session data stored in the database.
   * @param newSessionData The new session data to store.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the session data is updated.
   */
  updateSessionDataInDatabase(newSessionData: any, userContext?: Record<string, any>): Promise<any>;

  /**
   * Returns the user ID of the logged-in user.
   * @param userContext Optional context object for additional data.
   * @returns The user ID as a string.
   */
  getUserId(userContext?: Record<string, any>): string;

  /**
   * Returns the `RecipeUserId` object for the session. It represents the user ID of the specific login method for this user.
   * @param userContext Optional context object for additional data.
   * @returns The `RecipeUserId`.
   */
  getRecipeUserId(userContext?: Record<string, any>): RecipeUserId;

  /**
   * Returns the tenant ID of the session. The default value is "public" if multi-tenancy is not used.
   * @param userContext Optional context object for additional data.
   * @returns The tenant ID as a string.
   */
  getTenantId(userContext?: Record<string, any>): string;

  /**
   * Returns the access token's payload for this session. This includes user-defined claims, standard claims, and SuperTokens specific ones.
   * @param userContext Optional context object for additional data.
   * @returns The access token payload.
   */
  getAccessTokenPayload(userContext?: Record<string, any>): any;

  /**
   * Returns the `sessionHandle` for this session, a unique string constant for each session.
   * @param userContext Optional context object for additional data.
   * @returns The session handle as a string.
   */
  getHandle(userContext?: Record<string, any>): string;

  /**
   * Returns an object containing the raw string representation of all tokens associated with the session, along with an update status.
   * @returns An object with accessToken, refreshToken, antiCsrfToken, frontToken, and accessAndFrontTokenUpdated.
   */
  getAllSessionTokensDangerously(): {
    accessToken: string;
    refreshToken: string | undefined;
    antiCsrfToken: string | undefined;
    frontToken: string;
    accessAndFrontTokenUpdated: boolean;
  };

  /**
   * Returns the raw string access token for this session.
   * @param userContext Optional context object for additional data.
   * @returns The access token as a string.
   */
  getAccessToken(userContext?: Record<string, any>): string;

  /**
   * Adds key/value pairs into a JSON object in the access token. Setting a key to null removes it from the payload.
   * @param accessTokenPayloadUpdate The updates to apply to the access token payload.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the payload is updated.
   */
  mergeIntoAccessTokenPayload(accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<void>;

  /**
   * Returns the time in milliseconds of when this session was created.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the creation time in milliseconds.
   */
  getTimeCreated(userContext?: Record<string, any>): Promise<number>;

  /**
   * Returns the time in milliseconds of when this session will expire if not refreshed.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the expiry time in milliseconds.
   */
  getExpiry(userContext?: Record<string, any>): Promise<number>;

  /**
   * Asserts the validity of custom session claims using provided validators.
   * @param claimValidators An array of session claim validators.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the claim assertions are complete.
   */
  assertClaims(claimValidators: SessionClaimValidator[], userContext?: Record<string, any>): Promise<void>;

  /**
   * Fetches and sets a custom claim in the session.
   * @param claim The session claim to fetch and set.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the fetched claim.
   */
  fetchAndSetClaim<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<void>;

  /**
   * Sets the value of a session claim.
   * @param claim The session claim to update.
   * @param value The new value for the claim.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the claim value is set.
   */
  setClaimValue<T>(claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<void>;

  /**
   * Gets the value of a session claim.
   * @param claim The session claim to retrieve the value for.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the claim value, or undefined if not found.
   */
  getClaimValue<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<T | undefined>;

  /**
   * Removes a session claim.
   * @param claim The session claim to remove.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the claim is removed.
   */
  removeClaim(claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<void>;

  /**
   * Attaches the session to a request-response cycle.
   * @param reqResInfo Information about the request-response.
   * @param userContext Optional context object for additional data.
   * @returns A promise or void once the session is attached.
   */
  attachToRequestResponse(reqResInfo: ReqResInfo, userContext?: Record<string, any>): Promise<void> | void;
}
```
</Tab>
<Tab title="Go" value="go">
```go check=false reason="public interface excerpt omits dependent SDK types"


type TypeSessionContainer struct {
	// Destroys this session in the database and on the frontend.
	RevokeSession func() error

	// Retrieves the session data stored in the database associated with the session.
	GetSessionDataInDatabase func() (map[string]interface{}, error)

	// Sets a new JSON object to the session data stored in the database.
	// `newSessionData` is the new session data to store.
	UpdateSessionDataInDatabase func(newSessionData map[string]interface{}) error 

	// Returns the user ID of the logged-in user.
	GetUserID func() string

	// Returns the tenant ID of the session.
	// Default value is "public" if multi-tenancy is not used.
	GetTenantId func() string

	// Returns the access token's payload for this session.
	// Includes user-defined claims, standard claims, and SuperTokens specific ones.
	GetAccessTokenPayload func() map[string]interface{}

	// Returns the `sessionHandle` for this session,
	// a unique string constant for each session.
	GetHandle func() string

	// Returns an object containing the raw string representation
	// of all tokens associated with the session, along with an update status.
	GetAllSessionTokensDangerously func() SessionTokens

	// Returns the raw string access token for this session.
	GetAccessToken func() string

	// Returns the time in milliseconds of when this session was created.
	GetTimeCreated func() (uint64, error)

	// Returns the time in milliseconds of when this session will expire if not refreshed.
	GetExpiry func() (uint64, error)

	// Context-aware methods that provide the same functionality as their counterparts above while considering user context

	// Destroys this session in the database and on the frontend with user context.
	RevokeSessionWithContext func(userContext supertokens.UserContext) error

	// Retrieves the session data stored in the database associated with the session with user context.
	GetSessionDataInDatabaseWithContext func(userContext supertokens.UserContext) (map[string]interface{}, error)

	// Sets a new JSON object to the session data stored in the database with user context.
	UpdateSessionDataInDatabaseWithContext func(newSessionData map[string]interface{}, userContext supertokens.UserContext) error

	// Returns the user ID of the logged-in user with user context.
	GetUserIDWithContext func(userContext supertokens.UserContext) string

	// Returns the tenant ID of the session with user context.
	GetTenantIdWithContext func(userContext supertokens.UserContext) string

	// Returns the access token's payload for this session with user context.
	GetAccessTokenPayloadWithContext func(userContext supertokens.UserContext) map[string]interface{}

	// Returns the `sessionHandle` for this session with user context.
	GetHandleWithContext func(userContext supertokens.UserContext) string

	// Returns the raw string access token for this session with user context.
	GetAccessTokenWithContext func(userContext supertokens.UserContext) string

	// Returns the time in milliseconds of when this session was created with user context.
	GetTimeCreatedWithContext func(userContext supertokens.UserContext) (uint64, error)

	// Returns the time in milliseconds of when this session will expire if not refreshed with user context.
	GetExpiryWithContext func(userContext supertokens.UserContext) (uint64, error)

	// Adds key/value pairs into a JSON object in the access token with user context.
	// Setting a key to nil removes it from the payload.
	MergeIntoAccessTokenPayloadWithContext func(accessTokenPayloadUpdate map[string]interface{}, userContext supertokens.UserContext) error

	// Asserts the validity of custom session claims using provided validators with user context.
	AssertClaimsWithContext func(claimValidators []claims.SessionClaimValidator, userContext supertokens.UserContext) error

	// Fetches and sets a custom claim in the session with user context.
	FetchAndSetClaimWithContext func(claim *claims.TypeSessionClaim, userContext supertokens.UserContext) error

	// Sets the value of a session claim with user context.
	SetClaimValueWithContext func(claim *claims.TypeSessionClaim, value interface{}, userContext supertokens.UserContext) error

	// Gets the value of a session claim with user context.
	// Returns the value or nil if not found.
	GetClaimValueWithContext func(claim *claims.TypeSessionClaim, userContext supertokens.UserContext) interface{}

	// Removes a session claim with user context.
	RemoveClaimWithContext func(claim *claims.TypeSessionClaim, userContext supertokens.UserContext) error

	// Attaches the session to a request-response cycle with user context.
	AttachToRequestResponseWithContext func(info RequestResponseInfo, userContext supertokens.UserContext) error

	// Adds key/value pairs into a JSON object in the access token.
	// Setting a key to nil removes it from the payload.
	MergeIntoAccessTokenPayload func(accessTokenPayloadUpdate map[string]interface{}) error

	// Asserts the validity of custom session claims using provided validators.
	AssertClaims func(claimValidators []claims.SessionClaimValidator) error

	// Fetches and sets a custom claim in the session.
	FetchAndSetClaim func(claim *claims.TypeSessionClaim) error

	// Sets the value of a session claim.
	SetClaimValue func(claim *claims.TypeSessionClaim, value interface{}) error

	// Gets the value of a session claim.
	// Returns the value or nil if not found.
	GetClaimValue func(claim *claims.TypeSessionClaim) interface{}

	// Removes a session claim.
	RemoveClaim func(claim *claims.TypeSessionClaim) error

	// Attaches the session to a request-response cycle.
	AttachToRequestResponse func(info RequestResponseInfo) error
}
```
</Tab>
<Tab title="Python" value="python">
```python
# exclude-from-type-checking

class Session:
    # Destroys this session in the database and on the frontend.
    # Optional user_context can be used for additional contextual data.
    async def revoke_session(self, user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Retrieves the session data stored in the database associated with the session.
    # Optional user_context can be used for additional contextual data.
    async def get_session_data_from_database(self, user_context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        pass

    # Sets a new JSON object to the session data stored in the database.
    # `new_session_data` is the new session data to store.
    # Optional user_context can be used for additional contextual data.
    async def update_session_data_in_database(self, new_session_data: Dict[str, Any], user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Returns the user ID of the logged-in user.
    # Optional user_context can be used for additional contextual data.
    def get_user_id(self, user_context: Optional[Dict[str, Any]] = None) -> str:
        pass

    # Returns the `RecipeUserId` object for the session.
    # This represents the user ID of the specific login method for this user.
    # Optional user_context can be used for additional contextual data.
    def get_recipe_user_id(self, user_context: Optional[Dict[str, Any]] = None) -> RecipeUserId:
        pass

    # Returns the tenant ID of the session.
    # Default value is "public" if multi-tenancy is not used.
    # Optional user_context can be used for additional contextual data.
    def get_tenant_id(self, user_context: Optional[Dict[str, Any]] = None) -> str:
        pass

    # Returns the access token's payload for this session.
    # Includes user-defined claims, standard claims, and SuperTokens specific ones.
    # Optional user_context can be used for additional contextual data.
    def get_access_token_payload(self, user_context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        pass

    # Returns the `sessionHandle` for this session,
    # a unique string constant for each session.
    # Optional user_context can be used for additional contextual data.
    def get_handle(self, user_context: Optional[Dict[str, Any]] = None) -> str:
        pass

    # Returns an object containing the raw string representation
    # of all tokens associated with the session, along with an update status.
    def get_all_session_tokens_dangerously(self) -> GetSessionTokensDangerouslyDict:
        pass

    # Returns the raw string access token for this session.
    # Optional user_context can be used for additional contextual data.
    def get_access_token(self, user_context: Optional[Dict[str, Any]] = None) -> str:
        pass

    # Adds key/value pairs into a JSON object in the access token.
    # Setting a key to None removes it from the payload.
    # `access_token_payload_update` contains the updates to apply.
    # Optional user_context can be used for additional contextual data.
    async def merge_into_access_token_payload(self, access_token_payload_update: JSONObject, user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Returns the time in milliseconds of when this session was created.
    # Optional user_context can be used for additional contextual data.
    async def get_time_created(self, user_context: Optional[Dict[str, Any]] = None) -> int:
        pass

    # Returns the time in milliseconds of when this session will expire if not refreshed.
    # Optional user_context can be used for additional contextual data.
    async def get_expiry(self, user_context: Optional[Dict[str, Any]] = None) -> int:
        pass

    # Asserts the validity of custom session claims using provided validators.
    # `claim_validators` is an array of session claim validators.
    # Optional user_context can be used for additional contextual data.
    async def assert_claims(self, claim_validators: List[SessionClaimValidator], user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Fetches and sets a custom claim in the session.
    # `claim` is the session claim to fetch and set.
    # Optional user_context can be used for additional contextual data.
    async def fetch_and_set_claim(self, claim: SessionClaim[Any], user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Sets the value of a session claim.
    # `claim` is the session claim to update.
    # `value` is the new value for the claim.
    # Optional user_context can be used for additional contextual data.
    async def set_claim_value(self, claim: SessionClaim[_T], value: _T, user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Gets the value of a session claim.
    # `claim` is the session claim to retrieve the value for.
    # Optional user_context can be used for additional contextual data.
    # Returns a promise that resolves with the claim value, or None if not found.
    async def get_claim_value(self, claim: SessionClaim[_T], user_context: Optional[Dict[str, Any]] = None) -> Union[_T, None]:
        pass

    # Removes a session claim.
    # `claim` is the session claim to remove.
    # Optional user_context can be used for additional contextual data.
    async def remove_claim(self, claim: SessionClaim[Any], user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Attaches the session to a request-response cycle.
    # `req_res_info` contains information about the request-response.
    # user_context provides contextual data for request processing.
    async def attach_to_request_response(self, request: BaseRequest, transfer_method: TokenTransferMethod, user_context: Dict[str, Any]) -> None:
        pass
```
</Tab>
</CodeGroup>

<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
<div>

###   `getSessionDataFromDatabase` vs `getAccessTokenPayload`

</div>

|                               | `getSessionDataFromDatabase` | `getAccessTokenPayload` |
|--------------------------------------|-----------------------------|-------------------------|
| **Source of Data**                   | Queries SuperTokens Core database | Reads directly from the access token in the request |
| **Speed**                            | Slower (requires a network call) | Faster (no network call required) |
| **Data Sensitivity**                 | Secure—data is not exposed to the frontend | The access token includes data, which is accessible to the frontend |
| **Use Case**                         | Best for storing sensitive session-related data | Best for frequently accessed data like user roles |
| **Persistence**                      | Updated via `updateSessionDataInDatabase` | Updated via `mergeIntoAccessTokenPayload` |
</ContentOption>
<ContentOption title="Go" value="go">
<div>

### `GetSessionDataFromDatabase` vs `GetAccessTokenPayload`

</div>

|                               | `GetSessionDataFromDatabase` | `GetAccessTokenPayload` |
|--------------------------------------|-----------------------------|-------------------------|
| **Source of Data**                   | Queries SuperTokens Core database | Reads directly from the access token in the request |
| **Speed**                            | Slower (requires a network call) | Faster (no network call required) |
| **Data Sensitivity**                 | Secure—data is not exposed to the frontend | The access token includes data, which is accessible to the frontend |
| **Use Case**                         | Best for storing sensitive session-related data | Best for frequently accessed data like user roles |
| **Persistence**                      | Updated via `UpdateSessionDataInDatabase` | Updated via `MergeIntoAccessTokenPayload` |
</ContentOption>
<ContentOption title="Python" value="python">
<div>

### `get_session_data_from_database` vs `get_access_token_payload`

</div>

|                               | `get_session_data_from_database` | `get_access_token_payload` |
|--------------------------------------|-----------------------------|-------------------------|
| **Source of Data**                   | Queries SuperTokens Core database | Reads directly from the access token in the request |
| **Speed**                            | Slower (requires a network call) | Faster (no network call required) |
| **Data Sensitivity**                 | Secure—data is not exposed to the frontend | The access token includes data, which is accessible to the frontend |
| **Use Case**                         | Best for storing sensitive session-related data | Best for frequently accessed data like user roles |
| **Persistence**                      | Updated via `update_session_data_in_database` | Updated via `merge_into_access_token_payload` |
</ContentOption>
</DependentContent>
</AccordionItem>
</Accordion>


### Optional session verification

To make an API endpoint accessible even if there is no session update the middleware call to mark the session as not required.

<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";

let app = express();

app.post("/like-comment", verifySession({ sessionRequired: false }), (req: SessionRequest, res) => {
  if (req.session !== undefined) {
    let userId = req.session.getUserId();
  } else {
    // user is not logged in...
  }
});
```
</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";

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

server.route({
  path: "/like-comment",
  method: "post",
  options: {
    pre: [
      {
        method: verifySession({ sessionRequired: false }),
      },
    ],
  },
  handler: async (req: SessionRequest, res) => {
    if (req.session !== undefined) {
      let userId = req.session.getUserId();
    } else {
      // user is not logged in...
    }
  },
});
```
</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";

let fastify = Fastify();

fastify.post(
  "/like-comment",
  {
    preHandler: verifySession({ sessionRequired: false }),
  },
  (req: SessionRequest, res) => {
    if (req.session !== undefined) {
      let userId = req.session.getUserId();
    } else {
      // user is not logged in...
    }
  },
);
```
</ContentOption>
<ContentOption title="Aws Lambda" value="aws-lambda">
```tsx
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";
import { SessionEventV2 } from "supertokens-node/framework/awsLambda";

async function likeComment(awsEvent: SessionEventV2) {
  if (awsEvent.session !== undefined) {
    let userId = awsEvent.session.getUserId();
  } else {
    // user is not logged in...
  }
}

exports.handler = verifySession(likeComment, { sessionRequired: false });
```
</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";

let router = new KoaRouter();

router.post("/like-comment", verifySession({ sessionRequired: false }), (ctx: SessionContext, next) => {
  if (ctx.session !== undefined) {
    let userId = ctx.session.getUserId();
  } else {
    // user is not logged in...
  }
});
```
</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 { SessionContext } from "supertokens-node/framework/loopback";

class LikeComment {
  constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) {}
  @post("/like-comment")
  @intercept(verifySession({ sessionRequired: false }))
  @response(200)
  handler() {
    let session = (this.ctx as SessionContext).session;
    if (session !== undefined) {
      let userId = session.getUserId();
    } else {
      // user is not logged in...
    }
  }
}
```
</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";

export default async function likeComment(req: any, res: any) {
  await superTokensNextWrapper(
    async (next) => {
      await verifySession({ sessionRequired: false })(req, res, next);
    },
    req,
    res,
  );

  let session = (req as SessionRequest).session;

  if (session !== undefined) {
    let userId = session.getUserId();
    // session exists
  } else {
    // session doesn't exist
  }
  //....
}
```

</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 { OptionalAuthGuard } from "./auth/optionalAuth.guard";

@Controller()
export class ExampleController {
  @Post("example")
  @UseGuards(new OptionalAuthGuard()) // For more information about this guard please read our NestJS guide.
  async postExample(@Session() session: SessionContainer): Promise<boolean> {
    if (session !== undefined) {
      let userId = session.getUserId();
      // session exists
    } else {
      // session doesn't exist
    }

    //....
    return true;
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Go" value="go">
<DependentContent group="go-frameworks" label="Go framework">
<ContentOption title="HTTP" value="http">
```go
import (
	"fmt"
	"net/http"

	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
)

func main() {

	_ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		// Wrap the API handler in session.VerifySession
		sessionRequired := false
		session.VerifySession(&sessmodels.VerifySessionOptions{
			SessionRequired: &sessionRequired,
		}, likeCommentAPI).ServeHTTP(rw, r)
	})
}

func likeCommentAPI(w http.ResponseWriter, r *http.Request) {
	// retrieve the session object as shown below
	sessionContainer := session.GetSessionFromRequestContext(r.Context())

	userID := sessionContainer.GetUserID()

	fmt.Println(userID)
}
```
</ContentOption>
<ContentOption title="Gin" value="gin">
```go
import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
)

func main() {
	router := gin.New()

	// Wrap the API handler in session.VerifySession
	sessionRequired := false
	router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{
		SessionRequired: &sessionRequired,
	}), likeCommentAPI)
}

// 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 likeCommentAPI(c *gin.Context) {
	// retrieve the session object as shown below
	sessionContainer := session.GetSessionFromRequestContext(c.Request.Context())

	userID := sessionContainer.GetUserID()

	fmt.Println(userID)
}
```
</ContentOption>
<ContentOption title="Chi" value="chi">
```go
import (
	"fmt"
	"net/http"

	"github.com/go-chi/chi"
	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
)

func main() {
	r := chi.NewRouter()

	// Wrap the API handler in session.VerifySession
	sessionRequired := false
	r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{
		SessionRequired: &sessionRequired,
	}, likeCommentAPI))
}

func likeCommentAPI(w http.ResponseWriter, r *http.Request) {
	// retrieve the session object as shown below
	sessionContainer := session.GetSessionFromRequestContext(r.Context())

	userID := sessionContainer.GetUserID()

	fmt.Println(userID)
}
```
</ContentOption>
<ContentOption title="Mux" value="mux">
```go
import (
	"fmt"
	"net/http"

	"github.com/gorilla/mux"
	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
)

func main() {
	router := mux.NewRouter()

	// Wrap the API handler in session.VerifySession
	sessionRequired := false
	router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{
		SessionRequired: &sessionRequired,
	}, likeCommentAPI)).Methods(http.MethodPost)
}

func likeCommentAPI(w http.ResponseWriter, r *http.Request) {
	// retrieve the session object as shown below
	sessionContainer := session.GetSessionFromRequestContext(r.Context())

	userID := sessionContainer.GetUserID()

	fmt.Println(userID)
}
```
</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 typing import Optional

from fastapi import Depends

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


@app.post("/like_comment")  
async def like_comment(
    session: Optional[SessionContainer] = Depends(
        verify_session(session_required=False)
    ),
):
    if session is not None:
        user_id = session.get_user_id()
        print(user_id)  # TODO..
    else:
        pass  # user is not logged in
```
</ContentOption>
<ContentOption title="Flask" value="flask">
```python check=false reason="route fragment assumes an existing framework application"
from typing import Union

from flask import g

from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.framework.flask import verify_session


@app.route('/update-jwt', methods=['POST']) 
@verify_session(session_required=False)
def like_comment():
    session: Union[SessionContainer, None] = g.supertokens 

    if session is not None:
        user_id = session.get_user_id()
        print(user_id) # TODO..
    else:
        pass # user is not logged in
```
</ContentOption>
<ContentOption title="Django" value="django">
```python check=false reason="session attribute is injected by framework middleware"
from typing import Optional, cast

from django.http import HttpRequest

from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.framework.django.asyncio import verify_session


@verify_session(session_required=False)
async def like_comment(request: HttpRequest):
    session: Optional[SessionContainer] = cast(Optional[SessionContainer], request.supertokens) 

    if session is not None:
        user_id = session.get_user_id()
        print(user_id) # TODO..
    else:
        pass # user is not logged in
```
</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 { 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 });
      }

      if (session !== undefined) {
        let userId = session.getUserId();
        // session exists
      } else {
        // session doesn't exist
      }
      //....
      return NextResponse.json({});
    },
    { sessionRequired: false },
  );
}
```

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

### Verify the claims of a session

To check if there are certain claims in the session as part of the verification process you can override the session validators.
For example, you may want to check that the session has the `admin` role claim for certain APIs, or that the user has completed MFA, multi-factor authentication.
You can achieve this by including the user role claim validator in the middleware `global validators` option.
The `global validators` represent other validators that apply to all API routes by default.
This may include things like a validator that ensures that the user's email is verified.


<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>


:::tip[Feature]
You can also [build your own custom claim validators](/additional-verification/session-verification/claim-validation#using-session-claims) based on your app's requirements.
:::

---

## Using `Get Session`

The `Get Session` function performs the same verification as the middleware, but it does not complete error responses on
its own. It can still attach updated access, front, or anti-CSRF tokens to the supplied response. It throws errors that you
can catch and handle.
If these errors remain unhandled, the SuperTokens error handler catches these errors and writes to the client (like the `verifySession` middleware).

You should use this function if your framework does not support middlewares or if you want additional control over error management.

<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 Session from "supertokens-node/recipe/session";

let app = express();

app.post("/like-comment", async (req, res, next) => {
  try {
    let session = await Session.getSession(req, res);

    let userId = session.getUserId();
    //....
  } catch (err) {
    next(err);
  }
});
```
</ContentOption>
<ContentOption title="Hapi" value="hapi">
```tsx
import Hapi from "@hapi/hapi";
import Session from "supertokens-node/recipe/session";

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

server.route({
  path: "/like-comment",
  method: "post",
  handler: async (req, res) => {
    let session = await Session.getSession(req, res);

    let userId = session.getUserId();
    //...
  },
});
```
</ContentOption>
<ContentOption title="Fastify" value="fastify">
```tsx
import Fastify from "fastify";
import Session from "supertokens-node/recipe/session";

let fastify = Fastify();

fastify.post("/like-comment", async (req, res) => {
  let session = await Session.getSession(req, res);

  let userId = session.getUserId();
  //....
});
```
</ContentOption>
<ContentOption title="Aws Lambda" value="aws-lambda">
```tsx
import Session from "supertokens-node/recipe/session";
import { middleware } from "supertokens-node/framework/awsLambda";
import { SessionEvent } from "supertokens-node/framework/awsLambda";

async function likeComment(awsEvent: SessionEvent) {
  let session = await Session.getSession(awsEvent, awsEvent);

  let userId = session.getUserId();
  //....
}

exports.handler = middleware(likeComment);
```
</ContentOption>
<ContentOption title="Koa" value="koa">
```tsx
import KoaRouter from "koa-router";
import Session from "supertokens-node/recipe/session";

let router = new KoaRouter();

router.post("/like-comment", async (ctx, next) => {
  let session = await Session.getSession(ctx, ctx);

  let userId = session.getUserId();
  //....
});
```
</ContentOption>
<ContentOption title="LoopBack" value="loopback">
```tsx
import { inject } from "@loopback/core";
import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest";
import Session from "supertokens-node/recipe/session";

class LikeComment {
  constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) {}
  @post("/like-comment")
  @response(200)
  async handler() {
    let session = await Session.getSession(this.ctx, this.ctx);

    let userId = session.getUserId();
    //....
  }
}
```
</ContentOption>
<ContentOption title="Next.js" value="nextjs">
<ConditionalContent propertyName="nextjsRouterType" condition="pages-router">

```tsx
import { superTokensNextWrapper } from "supertokens-node/nextjs";
import Session from "supertokens-node/recipe/session";
import { SessionRequest } from "supertokens-node/framework/express";

export default async function likeComment(req: SessionRequest, res: any) {
  let session = await superTokensNextWrapper(
    async (next) => {
      return await Session.getSession(req, res);
    },
    req,
    res,
  );

  let userId = session.getUserId();
  //....
}
```

</ConditionalContent>
</ContentOption>
<ContentOption title="Nestjs" value="nestjs">
```tsx
import { Controller, Post, UseGuards, Req, Res } from "@nestjs/common";
import type { Request, Response } from "express";
import Session from "supertokens-node/recipe/session";

@Controller()
export class ExampleController {
  @Post("example")
  async postExample(@Req() req: Request, @Res({ passthrough: true }) res: Response): Promise<boolean> {
    // This should be done inside a parameter decorator, for more information please read our NestJS guide.
    const session = await Session.getSession(req, res);

    const userId = session.getUserId();
    //....
    return true;
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Go" value="go">
```go
import (
	"fmt"
	"net/http"

	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func likeCommentAPI(w http.ResponseWriter, r *http.Request) {
	sessionContainer, err := session.GetSession(r, w, nil)

	if err != nil {
		err = supertokens.ErrorHandler(err, r, w)
		if err != nil {
			// TODO: send 500 to client
		}
		return
	}

	userID := sessionContainer.GetUserID()

	// TODO: API logic...
	fmt.Println(userID)
}
```
</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.requests import Request

from supertokens_python.recipe.session.asyncio import get_session


@app.post('/like-comment') 
async def like_comment(request: Request):
    session = await get_session(request)

    if session is None:
        raise Exception("Should never come here")

    user_id = session.get_user_id()

    print(user_id)
    # TODO
```
</ContentOption>
<ContentOption title="Flask" value="flask">
```python check=false reason="route fragment assumes an existing framework application"
from flask import request

from supertokens_python.recipe.session.syncio import get_session


@app.route('/like-comment', methods=['POST']) 
def like_comment():
    session = get_session(request)

    if session is None:
        raise Exception("Should never come here")

    user_id = session.get_user_id()

    print(user_id)
    # TODO
```
</ContentOption>
<ContentOption title="Django" value="django">
```python
from django.http import HttpRequest

from supertokens_python.recipe.session.asyncio import get_session


async def like_comment(request: HttpRequest):
    session = await get_session(request)
    if session is None:
        raise Exception("Should never come here")

    user_id = session.get_user_id()

    print(user_id)  # TODO

```
</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 { withPreParsedRequestResponse } from "supertokens-node/nextjs";
import { CollectingResponse, PreParsedRequest } from "supertokens-node/framework/custom";
import Session from "supertokens-node/recipe/session";
import { backendConfig } from "@/app/config/backend";

SuperTokens.init(backendConfig());

export function POST(request: NextRequest) {
  return withPreParsedRequestResponse(
    request,
    async (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => {
      const session = await Session.getSession(baseRequest, baseResponse);
      let userId = session.getUserId();
      return NextResponse.json({});
    },
  );
}
```

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

<Accordion>
<AccordionItem title="The Session object">
<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx check=false reason="public interface excerpt omits dependent SDK type declarations"
interface Session {
  /**
   * Destroys this session in the database and on the frontend.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the session is successfully revoked.
   */
  revokeSession(userContext?: Record<string, any>): Promise<void>;

  /**
   * Retrieves the session data stored in the database associated with the session.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the session data.
   */
  getSessionDataFromDatabase(userContext?: Record<string, any>): Promise<any>;

  /**
   * Sets a new JSON object to the session data stored in the database.
   * @param newSessionData The new session data to store.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the session data is updated.
   */
  updateSessionDataInDatabase(newSessionData: any, userContext?: Record<string, any>): Promise<any>;

  /**
   * Returns the user ID of the logged-in user.
   * @param userContext Optional context object for additional data.
   * @returns The user ID as a string.
   */
  getUserId(userContext?: Record<string, any>): string;

  /**
   * Returns the `RecipeUserId` object for the session. It represents the user ID of the specific login method for this user.
   * @param userContext Optional context object for additional data.
   * @returns The `RecipeUserId`.
   */
  getRecipeUserId(userContext?: Record<string, any>): RecipeUserId;

  /**
   * Returns the tenant ID of the session. The default value is "public" if multi-tenancy is not used.
   * @param userContext Optional context object for additional data.
   * @returns The tenant ID as a string.
   */
  getTenantId(userContext?: Record<string, any>): string;

  /**
   * Returns the access token's payload for this session. This includes user-defined claims, standard claims, and SuperTokens specific ones.
   * @param userContext Optional context object for additional data.
   * @returns The access token payload.
   */
  getAccessTokenPayload(userContext?: Record<string, any>): any;

  /**
   * Returns the `sessionHandle` for this session, a unique string constant for each session.
   * @param userContext Optional context object for additional data.
   * @returns The session handle as a string.
   */
  getHandle(userContext?: Record<string, any>): string;

  /**
   * Returns an object containing the raw string representation of all tokens associated with the session, along with an update status.
   * @returns An object with accessToken, refreshToken, antiCsrfToken, frontToken, and accessAndFrontTokenUpdated.
   */
  getAllSessionTokensDangerously(): {
    accessToken: string;
    refreshToken: string | undefined;
    antiCsrfToken: string | undefined;
    frontToken: string;
    accessAndFrontTokenUpdated: boolean;
  };

  /**
   * Returns the raw string access token for this session.
   * @param userContext Optional context object for additional data.
   * @returns The access token as a string.
   */
  getAccessToken(userContext?: Record<string, any>): string;

  /**
   * Adds key/value pairs into a JSON object in the access token. Setting a key to null removes it from the payload.
   * @param accessTokenPayloadUpdate The updates to apply to the access token payload.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the payload is updated.
   */
  mergeIntoAccessTokenPayload(accessTokenPayloadUpdate: JSONObject, userContext?: Record<string, any>): Promise<void>;

  /**
   * Returns the time in milliseconds of when this session was created.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the creation time in milliseconds.
   */
  getTimeCreated(userContext?: Record<string, any>): Promise<number>;

  /**
   * Returns the time in milliseconds of when this session will expire if not refreshed.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the expiry time in milliseconds.
   */
  getExpiry(userContext?: Record<string, any>): Promise<number>;

  /**
   * Asserts the validity of custom session claims using provided validators.
   * @param claimValidators An array of session claim validators.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the claim assertions are complete.
   */
  assertClaims(claimValidators: SessionClaimValidator[], userContext?: Record<string, any>): Promise<void>;

  /**
   * Fetches and sets a custom claim in the session.
   * @param claim The session claim to fetch and set.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the fetched claim.
   */
  fetchAndSetClaim<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<void>;

  /**
   * Sets the value of a session claim.
   * @param claim The session claim to update.
   * @param value The new value for the claim.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the claim value is set.
   */
  setClaimValue<T>(claim: SessionClaim<T>, value: T, userContext?: Record<string, any>): Promise<void>;

  /**
   * Gets the value of a session claim.
   * @param claim The session claim to retrieve the value for.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves with the claim value, or undefined if not found.
   */
  getClaimValue<T>(claim: SessionClaim<T>, userContext?: Record<string, any>): Promise<T | undefined>;

  /**
   * Removes a session claim.
   * @param claim The session claim to remove.
   * @param userContext Optional context object for additional data.
   * @returns A promise that resolves when the claim is removed.
   */
  removeClaim(claim: SessionClaim<any>, userContext?: Record<string, any>): Promise<void>;

  /**
   * Attaches the session to a request-response cycle.
   * @param reqResInfo Information about the request-response.
   * @param userContext Optional context object for additional data.
   * @returns A promise or void once the session is attached.
   */
  attachToRequestResponse(reqResInfo: ReqResInfo, userContext?: Record<string, any>): Promise<void> | void;
}
```
</Tab>
<Tab title="Go" value="go">
```go check=false reason="public interface excerpt omits dependent SDK types"


type TypeSessionContainer struct {
	// Destroys this session in the database and on the frontend.
	RevokeSession func() error

	// Retrieves the session data stored in the database associated with the session.
	GetSessionDataInDatabase func() (map[string]interface{}, error)

	// Sets a new JSON object to the session data stored in the database.
	// `newSessionData` is the new session data to store.
	UpdateSessionDataInDatabase func(newSessionData map[string]interface{}) error 

	// Returns the user ID of the logged-in user.
	GetUserID func() string

	// Returns the tenant ID of the session.
	// Default value is "public" if multi-tenancy is not used.
	GetTenantId func() string

	// Returns the access token's payload for this session.
	// Includes user-defined claims, standard claims, and SuperTokens specific ones.
	GetAccessTokenPayload func() map[string]interface{}

	// Returns the `sessionHandle` for this session,
	// a unique string constant for each session.
	GetHandle func() string

	// Returns an object containing the raw string representation
	// of all tokens associated with the session, along with an update status.
	GetAllSessionTokensDangerously func() SessionTokens

	// Returns the raw string access token for this session.
	GetAccessToken func() string

	// Returns the time in milliseconds of when this session was created.
	GetTimeCreated func() (uint64, error)

	// Returns the time in milliseconds of when this session will expire if not refreshed.
	GetExpiry func() (uint64, error)

	// Context-aware methods that provide the same functionality as their counterparts above while considering user context

	// Destroys this session in the database and on the frontend with user context.
	RevokeSessionWithContext func(userContext supertokens.UserContext) error

	// Retrieves the session data stored in the database associated with the session with user context.
	GetSessionDataInDatabaseWithContext func(userContext supertokens.UserContext) (map[string]interface{}, error)

	// Sets a new JSON object to the session data stored in the database with user context.
	UpdateSessionDataInDatabaseWithContext func(newSessionData map[string]interface{}, userContext supertokens.UserContext) error

	// Returns the user ID of the logged-in user with user context.
	GetUserIDWithContext func(userContext supertokens.UserContext) string

	// Returns the tenant ID of the session with user context.
	GetTenantIdWithContext func(userContext supertokens.UserContext) string

	// Returns the access token's payload for this session with user context.
	GetAccessTokenPayloadWithContext func(userContext supertokens.UserContext) map[string]interface{}

	// Returns the `sessionHandle` for this session with user context.
	GetHandleWithContext func(userContext supertokens.UserContext) string

	// Returns the raw string access token for this session with user context.
	GetAccessTokenWithContext func(userContext supertokens.UserContext) string

	// Returns the time in milliseconds of when this session was created with user context.
	GetTimeCreatedWithContext func(userContext supertokens.UserContext) (uint64, error)

	// Returns the time in milliseconds of when this session will expire if not refreshed with user context.
	GetExpiryWithContext func(userContext supertokens.UserContext) (uint64, error)

	// Adds key/value pairs into a JSON object in the access token with user context.
	// Setting a key to nil removes it from the payload.
	MergeIntoAccessTokenPayloadWithContext func(accessTokenPayloadUpdate map[string]interface{}, userContext supertokens.UserContext) error

	// Asserts the validity of custom session claims using provided validators with user context.
	AssertClaimsWithContext func(claimValidators []claims.SessionClaimValidator, userContext supertokens.UserContext) error

	// Fetches and sets a custom claim in the session with user context.
	FetchAndSetClaimWithContext func(claim *claims.TypeSessionClaim, userContext supertokens.UserContext) error

	// Sets the value of a session claim with user context.
	SetClaimValueWithContext func(claim *claims.TypeSessionClaim, value interface{}, userContext supertokens.UserContext) error

	// Gets the value of a session claim with user context.
	// Returns the value or nil if not found.
	GetClaimValueWithContext func(claim *claims.TypeSessionClaim, userContext supertokens.UserContext) interface{}

	// Removes a session claim with user context.
	RemoveClaimWithContext func(claim *claims.TypeSessionClaim, userContext supertokens.UserContext) error

	// Attaches the session to a request-response cycle with user context.
	AttachToRequestResponseWithContext func(info RequestResponseInfo, userContext supertokens.UserContext) error

	// Adds key/value pairs into a JSON object in the access token.
	// Setting a key to nil removes it from the payload.
	MergeIntoAccessTokenPayload func(accessTokenPayloadUpdate map[string]interface{}) error

	// Asserts the validity of custom session claims using provided validators.
	AssertClaims func(claimValidators []claims.SessionClaimValidator) error

	// Fetches and sets a custom claim in the session.
	FetchAndSetClaim func(claim *claims.TypeSessionClaim) error

	// Sets the value of a session claim.
	SetClaimValue func(claim *claims.TypeSessionClaim, value interface{}) error

	// Gets the value of a session claim.
	// Returns the value or nil if not found.
	GetClaimValue func(claim *claims.TypeSessionClaim) interface{}

	// Removes a session claim.
	RemoveClaim func(claim *claims.TypeSessionClaim) error

	// Attaches the session to a request-response cycle.
	AttachToRequestResponse func(info RequestResponseInfo) error
}
```
</Tab>
<Tab title="Python" value="python">
```python
# exclude-from-type-checking

class Session:
    # Destroys this session in the database and on the frontend.
    # Optional user_context can be used for additional contextual data.
    async def revoke_session(self, user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Retrieves the session data stored in the database associated with the session.
    # Optional user_context can be used for additional contextual data.
    async def get_session_data_from_database(self, user_context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        pass

    # Sets a new JSON object to the session data stored in the database.
    # `new_session_data` is the new session data to store.
    # Optional user_context can be used for additional contextual data.
    async def update_session_data_in_database(self, new_session_data: Dict[str, Any], user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Returns the user ID of the logged-in user.
    # Optional user_context can be used for additional contextual data.
    def get_user_id(self, user_context: Optional[Dict[str, Any]] = None) -> str:
        pass

    # Returns the `RecipeUserId` object for the session.
    # This represents the user ID of the specific login method for this user.
    # Optional user_context can be used for additional contextual data.
    def get_recipe_user_id(self, user_context: Optional[Dict[str, Any]] = None) -> RecipeUserId:
        pass

    # Returns the tenant ID of the session.
    # Default value is "public" if multi-tenancy is not used.
    # Optional user_context can be used for additional contextual data.
    def get_tenant_id(self, user_context: Optional[Dict[str, Any]] = None) -> str:
        pass

    # Returns the access token's payload for this session.
    # Includes user-defined claims, standard claims, and SuperTokens specific ones.
    # Optional user_context can be used for additional contextual data.
    def get_access_token_payload(self, user_context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        pass

    # Returns the `sessionHandle` for this session,
    # a unique string constant for each session.
    # Optional user_context can be used for additional contextual data.
    def get_handle(self, user_context: Optional[Dict[str, Any]] = None) -> str:
        pass

    # Returns an object containing the raw string representation
    # of all tokens associated with the session, along with an update status.
    def get_all_session_tokens_dangerously(self) -> GetSessionTokensDangerouslyDict:
        pass

    # Returns the raw string access token for this session.
    # Optional user_context can be used for additional contextual data.
    def get_access_token(self, user_context: Optional[Dict[str, Any]] = None) -> str:
        pass

    # Adds key/value pairs into a JSON object in the access token.
    # Setting a key to None removes it from the payload.
    # `access_token_payload_update` contains the updates to apply.
    # Optional user_context can be used for additional contextual data.
    async def merge_into_access_token_payload(self, access_token_payload_update: JSONObject, user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Returns the time in milliseconds of when this session was created.
    # Optional user_context can be used for additional contextual data.
    async def get_time_created(self, user_context: Optional[Dict[str, Any]] = None) -> int:
        pass

    # Returns the time in milliseconds of when this session will expire if not refreshed.
    # Optional user_context can be used for additional contextual data.
    async def get_expiry(self, user_context: Optional[Dict[str, Any]] = None) -> int:
        pass

    # Asserts the validity of custom session claims using provided validators.
    # `claim_validators` is an array of session claim validators.
    # Optional user_context can be used for additional contextual data.
    async def assert_claims(self, claim_validators: List[SessionClaimValidator], user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Fetches and sets a custom claim in the session.
    # `claim` is the session claim to fetch and set.
    # Optional user_context can be used for additional contextual data.
    async def fetch_and_set_claim(self, claim: SessionClaim[Any], user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Sets the value of a session claim.
    # `claim` is the session claim to update.
    # `value` is the new value for the claim.
    # Optional user_context can be used for additional contextual data.
    async def set_claim_value(self, claim: SessionClaim[_T], value: _T, user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Gets the value of a session claim.
    # `claim` is the session claim to retrieve the value for.
    # Optional user_context can be used for additional contextual data.
    # Returns a promise that resolves with the claim value, or None if not found.
    async def get_claim_value(self, claim: SessionClaim[_T], user_context: Optional[Dict[str, Any]] = None) -> Union[_T, None]:
        pass

    # Removes a session claim.
    # `claim` is the session claim to remove.
    # Optional user_context can be used for additional contextual data.
    async def remove_claim(self, claim: SessionClaim[Any], user_context: Optional[Dict[str, Any]] = None) -> None:
        pass

    # Attaches the session to a request-response cycle.
    # `req_res_info` contains information about the request-response.
    # user_context provides contextual data for request processing.
    async def attach_to_request_response(self, request: BaseRequest, transfer_method: TokenTransferMethod, user_context: Dict[str, Any]) -> None:
        pass
```
</Tab>
</CodeGroup>

<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
<div>

###   `getSessionDataFromDatabase` vs `getAccessTokenPayload`

</div>

|                               | `getSessionDataFromDatabase` | `getAccessTokenPayload` |
|--------------------------------------|-----------------------------|-------------------------|
| **Source of Data**                   | Queries SuperTokens Core database | Reads directly from the access token in the request |
| **Speed**                            | Slower (requires a network call) | Faster (no network call required) |
| **Data Sensitivity**                 | Secure—data is not exposed to the frontend | The access token includes data, which is accessible to the frontend |
| **Use Case**                         | Best for storing sensitive session-related data | Best for frequently accessed data like user roles |
| **Persistence**                      | Updated via `updateSessionDataInDatabase` | Updated via `mergeIntoAccessTokenPayload` |
</ContentOption>
<ContentOption title="Go" value="go">
<div>

### `GetSessionDataFromDatabase` vs `GetAccessTokenPayload`

</div>

|                               | `GetSessionDataFromDatabase` | `GetAccessTokenPayload` |
|--------------------------------------|-----------------------------|-------------------------|
| **Source of Data**                   | Queries SuperTokens Core database | Reads directly from the access token in the request |
| **Speed**                            | Slower (requires a network call) | Faster (no network call required) |
| **Data Sensitivity**                 | Secure—data is not exposed to the frontend | The access token includes data, which is accessible to the frontend |
| **Use Case**                         | Best for storing sensitive session-related data | Best for frequently accessed data like user roles |
| **Persistence**                      | Updated via `UpdateSessionDataInDatabase` | Updated via `MergeIntoAccessTokenPayload` |
</ContentOption>
<ContentOption title="Python" value="python">
<div>

### `get_session_data_from_database` vs `get_access_token_payload`

</div>

|                               | `get_session_data_from_database` | `get_access_token_payload` |
|--------------------------------------|-----------------------------|-------------------------|
| **Source of Data**                   | Queries SuperTokens Core database | Reads directly from the access token in the request |
| **Speed**                            | Slower (requires a network call) | Faster (no network call required) |
| **Data Sensitivity**                 | Secure—data is not exposed to the frontend | The access token includes data, which is accessible to the frontend |
| **Use Case**                         | Best for storing sensitive session-related data | Best for frequently accessed data like user roles |
| **Persistence**                      | Updated via `update_session_data_in_database` | Updated via `merge_into_access_token_payload` |
</ContentOption>
</DependentContent>
</AccordionItem>
</Accordion>

### Optional session verification

To make an API endpoint accessible even if there is no session update the middleware call to mark the session as not required.

<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 Session from "supertokens-node/recipe/session";

let app = express();

app.post("/like-comment", async (req, res, next) => {
  try {
    let session = await Session.getSession(req, res, { sessionRequired: false });

    if (session !== undefined) {
      let userId = session.getUserId();
    } else {
      // user is not logged in...
    }
    //....
  } catch (err) {
    next(err);
  }
});
```
</ContentOption>
<ContentOption title="Hapi" value="hapi">
```tsx
import Hapi from "@hapi/hapi";
import Session from "supertokens-node/recipe/session";

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

server.route({
  path: "/like-comment",
  method: "post",

  handler: async (req, res) => {
    let session = await Session.getSession(req, res, { sessionRequired: false });

    if (session !== undefined) {
      let userId = session.getUserId();
    } else {
      // user is not logged in...
    }

    //...
  },
});
```
</ContentOption>
<ContentOption title="Fastify" value="fastify">
```tsx
import Fastify from "fastify";
import Session from "supertokens-node/recipe/session";

let fastify = Fastify();

fastify.post("/like-comment", async (req, res) => {
  let session = await Session.getSession(req, res, { sessionRequired: false });

  if (session !== undefined) {
    let userId = session.getUserId();
  } else {
    // user is not logged in...
  }
  //....
});
```
</ContentOption>
<ContentOption title="Aws Lambda" value="aws-lambda">
```tsx
import Session from "supertokens-node/recipe/session";
import { middleware } from "supertokens-node/framework/awsLambda";
import { SessionEvent } from "supertokens-node/framework/awsLambda";

async function likeComment(awsEvent: SessionEvent) {
  let session = await Session.getSession(awsEvent, awsEvent, { sessionRequired: false });

  if (session !== undefined) {
    let userId = session.getUserId();
  } else {
    // user is not logged in...
  }

  //....
}

exports.handler = middleware(likeComment);
```
</ContentOption>
<ContentOption title="Koa" value="koa">
```tsx
import KoaRouter from "koa-router";
import Session from "supertokens-node/recipe/session";

let router = new KoaRouter();

router.post("/like-comment", async (ctx, next) => {
  let session = await Session.getSession(ctx, ctx, { sessionRequired: false });

  if (session !== undefined) {
    let userId = session.getUserId();
  } else {
    // user is not logged in...
  }

  //....
});
```
</ContentOption>
<ContentOption title="LoopBack" value="loopback">
```tsx
import { inject } from "@loopback/core";
import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest";
import Session from "supertokens-node/recipe/session";

class LikeComment {
  constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) {}
  @post("/like-comment")
  @response(200)
  async handler() {
    let session = await Session.getSession(this.ctx, this.ctx, { sessionRequired: false });

    if (session !== undefined) {
      let userId = session.getUserId();
    } else {
      // user is not logged in...
    }

    //....
  }
}
```
</ContentOption>
<ContentOption title="Next.js" value="nextjs">
<ConditionalContent propertyName="nextjsRouterType" condition="pages-router">

```tsx
import { superTokensNextWrapper } from "supertokens-node/nextjs";
import Session from "supertokens-node/recipe/session";
import { SessionRequest } from "supertokens-node/framework/express";

export default async function likeComment(req: SessionRequest, res: any) {
  let session = await superTokensNextWrapper(
    async (next) => {
      return await Session.getSession(req, res, { sessionRequired: false });
    },
    req,
    res,
  );

  if (session !== undefined) {
    let userId = session.getUserId();
  } else {
    // user is not logged in...
  }
  //....
}
```

</ConditionalContent>
</ContentOption>
<ContentOption title="Nestjs" value="nestjs">
```tsx
import { Controller, Post, UseGuards, Req, Res } from "@nestjs/common";
import type { Request, Response } from "express";
import Session from "supertokens-node/recipe/session";

@Controller()
export class ExampleController {
  @Post("example")
  async postExample(@Req() req: Request, @Res({ passthrough: true }) res: Response): Promise<boolean> {
    // This should be done inside a parameter decorator, for more information please read our NestJS guide.
    const session = await Session.getSession(req, res, { sessionRequired: false });

    if (session !== undefined) {
      const userId = session.getUserId();
    } else {
      // user is not logged in...
    }
    //....
    return true;
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Go" value="go">
```go
import (
	"fmt"
	"net/http"

	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func likeCommentAPI(w http.ResponseWriter, r *http.Request) {
	sessionRequired := false
	sessionContainer, err := session.GetSession(r, w, &sessmodels.VerifySessionOptions{
		SessionRequired: &sessionRequired,
	})

	if err != nil {
		err = supertokens.ErrorHandler(err, r, w)
		if err != nil {
			// TODO: send 500 to client
		}
		return
	}
    if sessionContainer != nil {
        // session exists
	    userID := sessionContainer.GetUserID()
        fmt.Println(userID)
    } else {
        // user is not logged in
    }
}
```
</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 Request

from supertokens_python.recipe.session.asyncio import get_session


@app.post("/like-comment")  
async def like_comment(request: Request):
    session = await get_session(request, session_required=False)

    if session is not None:
        user_id = session.get_user_id()
        print(user_id)  # TODO:
    else:
        pass  # user is not logged in

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

from supertokens_python.recipe.session.syncio import get_session


@app.route("/like-comment", methods=["POST"])  
def like_comment():
    session = get_session(request, session_required=False)

    if session is not None:
        user_id = session.get_user_id()
        print(user_id)  # TODO..
    else:
        pass  # user is not logged in

```
</ContentOption>
<ContentOption title="Django" value="django">
```python
from django.http import HttpRequest

from supertokens_python.recipe.session.asyncio import get_session


async def like_comment(request: HttpRequest):
    session = await get_session(request, session_required=False)

    if session is not None:
        user_id = session.get_user_id()
        print(user_id)  # TODO..
    else:
        pass  # user is not logged in

```
</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 { withPreParsedRequestResponse } from "supertokens-node/nextjs";
import { CollectingResponse, PreParsedRequest } from "supertokens-node/framework/custom";
import Session from "supertokens-node/recipe/session";
import { backendConfig } from "@/app/config/backend";

SuperTokens.init(backendConfig());

export function POST(request: NextRequest) {
  return withPreParsedRequestResponse(
    request,
    async (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => {
      const session = await Session.getSession(baseRequest, baseResponse, { sessionRequired: false });
      if (session !== undefined) {
        let userId = session.getUserId();
      } else {
        // user is not logged in...
      }
      return NextResponse.json({});
    },
  );
}
```

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

### Verify the claims of a session

To check if there are certain claims in the session as part of the verification process you can override the session validators.
For example, you may want to check that the session has the `admin` role claim for certain APIs, or that the user has completed MFA, multi-factor authentication.
This can be achieved by including the user role claim validator in the middleware `global validators` option.
The `global validators` represent other validators that apply to all API routes by default.
This may include things like a validator that ensures that the user's email is verified.

<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 Session from "supertokens-node/recipe/session";
import UserRoles from "supertokens-node/recipe/userroles";

let app = express();

app.post("/like-comment", async (req, res, next) => {
  try {
    let session = await Session.getSession(req, res, {
      overrideGlobalClaimValidators: async (globalValidators) => [
        ...globalValidators,
        UserRoles.UserRoleClaim.validators.includes("admin"),
        // UserRoles.PermissionClaim.validators.includes("edit")
      ],
    });

    let userId = session.getUserId();
    //....
  } catch (err) {
    next(err);
  }
});
```
</ContentOption>
<ContentOption title="Hapi" value="hapi">
```tsx
import Hapi from "@hapi/hapi";
import Session from "supertokens-node/recipe/session";
import UserRoles from "supertokens-node/recipe/userroles";

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

server.route({
  path: "/like-comment",
  method: "post",
  handler: async (req, res) => {
    let session = await Session.getSession(req, res, {
      overrideGlobalClaimValidators: async (globalValidators) => [
        ...globalValidators,
        UserRoles.UserRoleClaim.validators.includes("admin"),
        // UserRoles.PermissionClaim.validators.includes("edit")
      ],
    });

    let userId = session.getUserId();
    //...
  },
});
```
</ContentOption>
<ContentOption title="Fastify" value="fastify">
```tsx
import Fastify from "fastify";
import Session from "supertokens-node/recipe/session";
import UserRoles from "supertokens-node/recipe/userroles";

let fastify = Fastify();

fastify.post("/like-comment", async (req, res) => {
  let session = await Session.getSession(req, res, {
    overrideGlobalClaimValidators: async (globalValidators) => [
      ...globalValidators,
      UserRoles.UserRoleClaim.validators.includes("admin"),
      // UserRoles.PermissionClaim.validators.includes("edit")
    ],
  });

  let userId = session.getUserId();
  //....
});
```
</ContentOption>
<ContentOption title="Aws Lambda" value="aws-lambda">
```tsx
import Session from "supertokens-node/recipe/session";
import { middleware } from "supertokens-node/framework/awsLambda";
import { SessionEvent } from "supertokens-node/framework/awsLambda";
import UserRoles from "supertokens-node/recipe/userroles";

async function likeComment(awsEvent: SessionEvent) {
  let session = await Session.getSession(awsEvent, awsEvent, {
    overrideGlobalClaimValidators: async (globalValidators) => [
      ...globalValidators,
      UserRoles.UserRoleClaim.validators.includes("admin"),
      // UserRoles.PermissionClaim.validators.includes("edit")
    ],
  });

  let userId = session.getUserId();
  //....
}

exports.handler = middleware(likeComment);
```
</ContentOption>
<ContentOption title="Koa" value="koa">
```tsx
import KoaRouter from "koa-router";
import Session from "supertokens-node/recipe/session";
import UserRoles from "supertokens-node/recipe/userroles";

let router = new KoaRouter();

router.post("/like-comment", async (ctx, next) => {
  let session = await Session.getSession(ctx, ctx, {
    overrideGlobalClaimValidators: async (globalValidators) => [
      ...globalValidators,
      UserRoles.UserRoleClaim.validators.includes("admin"),
      // UserRoles.PermissionClaim.validators.includes("edit")
    ],
  });

  let userId = session.getUserId();
  //....
});
```
</ContentOption>
<ContentOption title="LoopBack" value="loopback">
```tsx
import { inject } from "@loopback/core";
import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest";
import Session from "supertokens-node/recipe/session";
import UserRoles from "supertokens-node/recipe/userroles";

class LikeComment {
  constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) {}
  @post("/like-comment")
  @response(200)
  async handler() {
    let session = await Session.getSession(this.ctx, this.ctx, {
      overrideGlobalClaimValidators: async (globalValidators) => [
        ...globalValidators,
        UserRoles.UserRoleClaim.validators.includes("admin"),
        // UserRoles.PermissionClaim.validators.includes("edit")
      ],
    });

    let userId = session.getUserId();
    //....
  }
}
```
</ContentOption>
<ContentOption title="Next.js" value="nextjs">
<ConditionalContent propertyName="nextjsRouterType" condition="pages-router">

```tsx
import { superTokensNextWrapper } from "supertokens-node/nextjs";
import Session from "supertokens-node/recipe/session";
import { SessionRequest } from "supertokens-node/framework/express";
import UserRoles from "supertokens-node/recipe/userroles";

export default async function likeComment(req: SessionRequest, res: any) {
  let session = await superTokensNextWrapper(
    async (next) => {
      return await Session.getSession(req, res, {
        overrideGlobalClaimValidators: async (globalValidators) => [
          ...globalValidators,
          UserRoles.UserRoleClaim.validators.includes("admin"),
          // UserRoles.PermissionClaim.validators.includes("edit")
        ],
      });
    },
    req,
    res,
  );

  let userId = session.getUserId();
  //....
}
```

</ConditionalContent>
</ContentOption>
<ContentOption title="Nestjs" value="nestjs">
```tsx
import { Controller, Post, UseGuards, Req, Res } from "@nestjs/common";
import type { Request, Response } from "express";
import Session from "supertokens-node/recipe/session";
import UserRoles from "supertokens-node/recipe/userroles";

@Controller()
export class ExampleController {
  @Post("example")
  async postExample(@Req() req: Request, @Res({ passthrough: true }) res: Response): Promise<boolean> {
    // This should be done inside a parameter decorator, for more information please read our NestJS guide.
    const session = await Session.getSession(req, res, {
      overrideGlobalClaimValidators: async (globalValidators) => [
        ...globalValidators,
        UserRoles.UserRoleClaim.validators.includes("admin"),
        // UserRoles.PermissionClaim.validators.includes("edit")
      ],
    });

    const userId = session.getUserId();
    //....
    return true;
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Go" value="go">
```go
import (
	"fmt"
	"net/http"

	"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/recipe/userroles/userrolesclaims"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func likeCommentAPI(w http.ResponseWriter, r *http.Request) {
	sessionContainer, err := session.GetSession(r, w, &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
        },
    })

	if err != nil {
		err = supertokens.ErrorHandler(err, r, w)
		if err != nil {
			// TODO: send 500 to client
		}
		return
	}

	userID := sessionContainer.GetUserID()

	// TODO: API logic...
	fmt.Println(userID)
}
```
</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.requests import Request

from supertokens_python.recipe.session.asyncio import get_session
from supertokens_python.recipe.userroles import UserRoleClaim


@app.post('/like-comment') 
async def like_comment(request: Request):
    session = await get_session(request,
        override_global_claim_validators=lambda global_validators, session, user_context: global_validators + \
        [UserRoleClaim.validators.includes("admin")])

    if session is None:
        raise Exception("Should never come here")

    user_id = session.get_user_id()

    print(user_id)
    # TODO
```
</ContentOption>
<ContentOption title="Flask" value="flask">
```python check=false reason="route fragment assumes an existing framework application"
from flask import request

from supertokens_python.recipe.session.syncio import get_session
from supertokens_python.recipe.userroles import UserRoleClaim


@app.route("/like-comment", methods=["POST"])  
def like_comment():
    session = get_session(
        request,
        override_global_claim_validators=lambda global_validators,
        session,
        user_context: global_validators + [UserRoleClaim.validators.includes("admin")],
    )

    if session is None:
        raise Exception("Should never come here")

    user_id = session.get_user_id()

    print(user_id)
    # TODO

```
</ContentOption>
<ContentOption title="Django" value="django">
```python
from django.http import HttpRequest

from supertokens_python.recipe.session.asyncio import get_session
from supertokens_python.recipe.userroles import UserRoleClaim


async def like_comment(request: HttpRequest):
    session = await get_session(
        request,
        override_global_claim_validators=lambda global_validators,
        session,
        user_context: global_validators + [UserRoleClaim.validators.includes("admin")],
    )
    if session is None:
        raise Exception("Should never come here")

    user_id = session.get_user_id()

    print(user_id)  # TODO

```
</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 { withPreParsedRequestResponse } from "supertokens-node/nextjs";
import { CollectingResponse, PreParsedRequest } from "supertokens-node/framework/custom";
import Session from "supertokens-node/recipe/session";
import UserRoles from "supertokens-node/recipe/userroles";
import { backendConfig } from "@/app/config/backend";

SuperTokens.init(backendConfig());

export function POST(request: NextRequest) {
  return withPreParsedRequestResponse(
    request,
    async (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => {
      const session = await Session.getSession(baseRequest, baseResponse, {
        overrideGlobalClaimValidators: async (globalValidators) => [
          ...globalValidators,
          UserRoles.UserRoleClaim.validators.includes("admin"),
          // UserRoles.PermissionClaim.validators.includes("edit")
        ],
      });
      let userId = session.getUserId();
      return NextResponse.json({});
    },
  );
}
```

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

:::tip[feature]
You can also [build your own custom claim validators](/additional-verification/session-verification/claim-validation#using-session-claims) based on your app's requirements.
:::

### Build your own middleware

Both these functions perform session verification.
However, `Verify Session` is a middleware that returns a reply directly to the frontend if the input access token is invalid or expired.
On the other hand, `Get Session` is a function that returns a session object on successful verification.
It throws an exception that you can handle if the access token expires or is invalid.

Internally, `Verify Session` uses `Get Session` in the following way:

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
import { VerifySessionOptions } from "supertokens-node/recipe/session/types";
import { errorHandler } from "supertokens-node/framework/express";
import { NextFunction, Request, Response } from "express";
import Session from "supertokens-node/recipe/session";
import { Error as SuperTokensError } from "supertokens-node";

function verifySession(options?: VerifySessionOptions) {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      (req as any).session = await Session.getSession(req, res, options);
      next();
    } catch (err) {
      if (SuperTokensError.isErrorFromSuperTokens(err)) {
        if (err.type === Session.Error.TRY_REFRESH_TOKEN) {
          // This means that the session exists, but the access token
          // has expired.
          // You can handle this in a custom way by sending a 401.
          // Or you can call the errorHandler middleware as shown below
        } else if (err.type === Session.Error.UNAUTHORISED) {
          // This means that the session does not exist anymore.
          // You can handle this in a custom way by sending a 401.
          // Or you can call the errorHandler middleware as shown below
        } else if (err.type === Session.Error.INVALID_CLAIMS) {
          // The user is missing some required claim.
          // You can pass the missing claims to the frontend and handle it there. Send a 403 to the frontend.
        }

        // OR you can use this errorHandler which will
        // handle all of the above errors in the default way
        errorHandler()(err, req, res, (err) => {
          next(err);
        });
      } else {
        next(err);
      }
    }
  };
}
```
</Tab>
<Tab title="Go" value="go">
```go
import (
	"context"
	"net/http"

	defaultErrors "errors"

	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/errors"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
	"github.com/supertokens/supertokens-golang/supertokens"
)

func VerifySession(options *sessmodels.VerifySessionOptions, otherHandler http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		session, err := session.GetSession(r, w, options)
		if err != nil {
			if defaultErrors.As(err, &errors.TryRefreshTokenError{}) {
				// This means that the session exists, but the access token
				// has expired.

				// You can handle this in a custom way by sending a 401.
				// Or you can call the errorHandler middleware as shown below
			} else if defaultErrors.As(err, &errors.UnauthorizedError{}) {
				// This means that the session does not exist anymore.

				// You can handle this in a custom way by sending a 401.
				// Or you can call the errorHandler middleware as shown below
			} else if defaultErrors.As(err, &errors.InvalidClaimError{}) {
				// The user is missing some required claim.
				// You can pass the missing claims to the frontend and handle it there. Send a 403 to the frontend.
			}

			// OR you can use this errorHandler which will
			// handle all of the above errors in the default way
			err = supertokens.ErrorHandler(err, r, w)
			if err != nil {
				// TODO: send a 500 error to the frontend
			}
			return
		}
		if session != nil {
			ctx := context.WithValue(r.Context(), sessmodels.SessionContext, session)
			otherHandler(w, r.WithContext(ctx))
		} else {
			otherHandler(w, r)
		}
	})
}
```
</Tab>
<Tab title="Python" value="python">
```python
from functools import wraps
from typing import Any, Callable, Dict, List, Optional, TypeVar, Union, cast

from supertokens_python.framework.flask.flask_request import FlaskRequest
from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.exceptions import (
    InvalidClaimsError,
    TryRefreshTokenError,
    UnauthorisedError,
)
from supertokens_python.recipe.session.interfaces import SessionClaimValidator
from supertokens_python.recipe.session.syncio import get_session
from supertokens_python.types import MaybeAwaitable

_T = TypeVar("_T", bound=Callable[..., Any])


def verify_session(
    session_required: bool = True,
    anti_csrf_check: Union[bool, None] = None,
    check_database: Optional[bool] = None,
    override_global_claim_validators: Optional[
        Callable[
            [List[SessionClaimValidator], SessionContainer, Dict[str, Any]],
            MaybeAwaitable[List[SessionClaimValidator]],
        ]
    ] = None,
) -> Callable[[_T], _T]:
    def session_verify(f: _T) -> _T:
        @wraps(f)
        def wrapped_function(*args: Any, **kwargs: Any):
            from flask import make_response, request

            baseRequest = FlaskRequest(request)
            try:
                session = get_session(
                    baseRequest,
                    session_required,
                    anti_csrf_check,
                    check_database,
                    override_global_claim_validators,
                )
            except Exception as e:
                if isinstance(e, TryRefreshTokenError):
                    # This means that the session exists, but the access token
                    # has expired.

                    # You can handle this in a custom way by sending a 401.
                    # Or you can call the errorHandler middleware as shown below
                    pass
                if isinstance(e, UnauthorisedError):
                    # This means that the session does not exist anymore.

                    # You can handle this in a custom way by sending a 401.
                    # Or you can call the errorHandler middleware as shown below
                    pass
                if isinstance(e, InvalidClaimsError):
                    # The user is missing some required claim.
                    # You can pass the missing claims to the frontend and handle it there. Send a 403 to the frontend.
                    pass

                # OR you can raise this error which will
                # handle all of the above errors in the default way
                raise e
            if session is None:
                if session_required:
                    raise Exception("Should never come here")
                baseRequest.set_session_as_none()
            else:
                baseRequest.set_session(session)
            response = make_response(f(*args, **kwargs))
            return response

        return cast(_T, wrapped_function)

    return session_verify

```
</Tab>
</CodeGroup>

<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
The `errorHandler` sends a `401` reply to the frontend if the `getSession` function throws an exception indicating that the session does not exist or if the access token has expired.
</ContentOption>
<ContentOption title="Go" value="go">
The `SuperTokens.ErrorHandler` sends a `401` reply to the frontend if the `getSession` function throws an exception indicating that the session does not exist or if the access token has expired.
</ContentOption>
<ContentOption title="Python" value="python">
If `get_session` throws an error (in case the input access token is invalid or has expired), then the SuperTokens middleware added to your app handles that exception. It sends a `401` to the frontend.
</ContentOption>
</DependentContent>

### Get the session using the `Access Token`

In the above snippets, `Get Session` requires the `request` object and, depending on your backend language and framework, may also require the `response` object.
Either way, this version of `Get Session` automatically reads from the request.
And automatically sets the response based on the update to the session tokens.
Whilst this is convenient, sometimes, you may not have the `request` or `response` objects, or you may not want SuperTokens to set the tokens in the response automatically.
In this case, you can use the `getSessionWithoutRequestResponse` function.

This function works similarly to `getSession`, except that it doesn't depend on the `request` or `response` objects.
It's your responsibility to provide this function the access token.
You must write the update tokens to the response if the tokens update during this API call.

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
import { VerifySessionOptions } from "supertokens-node/recipe/session/types";
import { SessionContainer } from "supertokens-node/recipe/session";
import Session from "supertokens-node/recipe/session";
import { Error as SuperTokensError } from "supertokens-node";

async function verifySession(accessToken: string, antiCsrfToken?: string, options?: VerifySessionOptions) {
  let session: SessionContainer | undefined;
  try {
    session = await Session.getSessionWithoutRequestResponse(accessToken, antiCsrfToken, options);
  } catch (err) {
    if (SuperTokensError.isErrorFromSuperTokens(err)) {
      if (err.type === Session.Error.TRY_REFRESH_TOKEN) {
        // This means that the session exists, but the access token
        // has expired.
        // You can handle this in a custom way by sending a 401.
        // Or you can call the errorHandler middleware as shown below
      } else if (err.type === Session.Error.UNAUTHORISED) {
        // This means that the session does not exist anymore.
        // You can handle this in a custom way by sending a 401.
        // Or you can call the errorHandler middleware as shown below
      } else if (err.type === Session.Error.INVALID_CLAIMS) {
        // The user is missing some required claim.
        // You can pass the missing claims to the frontend and handle it there. Send a 403 to the frontend.
      }
    }
    throw err;
  }
  if (session !== undefined) {
    // we can use the `session` container as we usually do..
    // TODO: API logic...

    // At the end of the API logic, we must fetch all the tokens from the session container
    // and set them in the response headers / cookies ourselves.
    const tokens = session.getAllSessionTokensDangerously();
    if (tokens.accessAndFrontTokenUpdated) {
      // TODO: set access token in response via tokens.accessToken
      // TODO: set front-token in response via tokens.frontToken
      if (tokens.antiCsrfToken) {
        // TODO: set anti-csrf token update in response via tokens.antiCsrfToken
      }
    }
  }
}
```
</Tab>
<Tab title="Go" value="go">
```go
import (
	defaultErrors "errors"

	"github.com/supertokens/supertokens-golang/recipe/session"
	"github.com/supertokens/supertokens-golang/recipe/session/errors"
	"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
)

func VerifySession(accessToken string, antiCsrfToken *string, options *sessmodels.VerifySessionOptions) error {
	session, err := session.GetSessionWithoutRequestResponse(accessToken, antiCsrfToken, options)
	if err != nil {
		if defaultErrors.As(err, &errors.TryRefreshTokenError{}) {
			// This means that the session exists, but the access token
			// has expired.

			// You can handle this in a custom way by sending a 401.
			// Or you can call the errorHandler middleware as shown below
		} else if defaultErrors.As(err, &errors.UnauthorizedError{}) {
			// This means that the session does not exist anymore.

			// You can handle this in a custom way by sending a 401.
			// Or you can call the errorHandler middleware as shown below
		} else if defaultErrors.As(err, &errors.InvalidClaimError{}) {
			// The user is missing some required claim.
			// You can pass the missing claims to the frontend and handle it there. Send a 403 to the frontend.
		} else {
			// TODO: send a 500 error to the frontend
		}
		return err
	}
	if session != nil {
		// we can use the `session` container as we usually do..
		// TODO: API logic...

		// At the end of the API logic, we must fetch all the tokens from the session container
		// and set them in the response headers / cookies ourselves.
		tokens := session.GetAllSessionTokensDangerously()
		if tokens.AccessAndFrontendTokenUpdated {
			// TODO: set access token in response via tokens.accessToken
			// TODO: set front-token in response via tokens.frontToken
			if tokens.AntiCsrfToken != nil {
				// TODO: set anti-csrf token update in response via *tokens.AntiCsrfToken
			}
		}
	}
	return nil
}
```
</Tab>
<Tab title="Python" value="python">
```python
from typing import Any, Callable, Dict, List, Optional, TypeVar

from supertokens_python.recipe.session import SessionContainer
from supertokens_python.recipe.session.exceptions import (
    InvalidClaimsError,
    TryRefreshTokenError,
    UnauthorisedError,
)
from supertokens_python.recipe.session.interfaces import SessionClaimValidator
from supertokens_python.recipe.session.syncio import (
    get_session_without_request_response,
)
from supertokens_python.types import MaybeAwaitable

_T = TypeVar("_T", bound=Callable[..., Any])


def verify_session(
    access_token: str,
    anti_csrf_token: Optional[str],
    anti_csrf_check: Optional[bool],
    session_required: Optional[bool],
    check_database: Optional[bool],
    override_global_claim_validators: Optional[
        Callable[
            [List[SessionClaimValidator], SessionContainer, Dict[str, Any]],
            MaybeAwaitable[List[SessionClaimValidator]],
        ]
    ] = None,
):
    try:
        session = get_session_without_request_response(
            access_token,
            anti_csrf_token,
            anti_csrf_check,
            session_required,
            check_database,
            override_global_claim_validators,
        )
    except Exception as e:
        if isinstance(e, TryRefreshTokenError):
            # This means that the session exists, but the access token
            # has expired.

            # You can handle this in a custom way by sending a 401.
            # Or you can call the errorHandler middleware as shown below
            pass
        if isinstance(e, UnauthorisedError):
            # This means that the session does not exist anymore.

            # You can handle this in a custom way by sending a 401.
            # Or you can call the errorHandler middleware as shown below
            pass
        if isinstance(e, InvalidClaimsError):
            # The user is missing some required claim.
            # You can pass the missing claims to the frontend and handle it there. Send a 403 to the frontend.
            pass

        # OR you can raise this error which will
        # handle all of the above errors in the default way
        raise e

    if session is not None:
        # we can use the `session` container as we usually do..
        # TODO: API logic...

        # At the end of the API logic, we must fetch all the tokens from the session container
        # and set them in the response headers / cookies ourselves.
        tokens = session.get_all_session_tokens_dangerously()
        if tokens["accessAndFrontTokenUpdated"]:
            # TODO: set access token in response via tokens["accessToken"]
            # TODO: set front-token in response via tokens["frontToken"]
            if tokens["antiCsrfToken"] is not None:
                # TODO: set anti-csrf token update in response via tokens["antiCsrfToken"]
                pass

```
</Tab>
</CodeGroup>


---

## Manual JWT verification

Use a released SuperTokens `verifySession`, `getSession`, or equivalent API whenever one is available. These APIs validate
more than the JWT signature and expiry. For example, when you already have an access-token string rather than framework
request and response objects, use the released `getSessionWithoutRequestResponse` API shown above.

Manual verification is only appropriate when your platform has no released SuperTokens backend SDK, or when an API gateway
cannot call one. Do not use mutable code snippets without pinned revisions as the verifier for a production system. Pin and
test the JWT library and verifier implementation you maintain.

A manual verifier must reject the token unless **all** of these checks pass:

1. Select the key by `kid` from `<YOUR_API_DOMAIN>/auth/jwt/jwks.json`, and restrict verification to the expected signing
   algorithm (`RS256`). Do not derive the accepted algorithm from the token-controlled header.
2. Verify the signature and expiry (`exp`).
3. Validate the token type using released SuperTokens semantics: if `stt` is present, require the numeric value `0`.
   Released Node.js SDK 24.0.3 also accepts a missing `stt` for backward compatibility. Only accept that absence while
   also validating a supported SuperTokens token header/version and that version's complete required payload shape. Reject
   every other `stt` value or type. A valid JWT signed by a key in the JWKS is not necessarily a session access token.
4. Validate the required session payload shape and field types. Current access tokens require string values for `sub`,
   `sessionHandle`, `refreshTokenHash1`, `rsub`, and `tId`, plus numeric `iat` and `exp`. Treat a changed or unknown token
   shape as invalid rather than accepting it partially.
5. Validate every application authorization claim required by the route, such as tenant, role, permission, email
   verification, or MFA state. Signature verification authenticates claims; it does not authorize the request.
6. For unsafe requests authenticated by cookies, perform the configured anti-CSRF check. With `VIA_CUSTOM_HEADER`, require
   the `rid: session` header. With `VIA_TOKEN`, compare the `anti-csrf` request header with `antiCsrfToken` in the payload.

The following access-token fields are managed by SuperTokens and must not be treated as application-defined fields:
`sub`, `iat`, `exp`, `sessionHandle`, `parentRefreshTokenHash1`, `refreshTokenHash1`, `antiCsrfToken`, `rsub`, `tId`, and
`stt`.

:::warning[Manual verification has important limitations]
A signature-only verifier does not check session revocation or run the backend SDK's global claim validators. If immediate
revocation matters, ask the Core for the authoritative session state using the
[Get Session Information](/references/cdi/session-recipe/getsessioninfo) route with the session handle, or the
[Verify Session](/references/cdi/session-recipe/verifysession) route with the access token and `checkDatabase` set to
`true`. These database-backed checks confirm whether the session still exists and has not been revoked; the Verify Session
route defaults `checkDatabase` to `false`, and local JWT verification alone cannot determine revocation. Claim refresh
behavior is claim-specific: each claim defines its own refresh policy, and some built-in claims have no default maximum age.
Do not assume that all claims refresh on a fixed five-minute interval.
:::

---

## See also

<CardGroup cols={3}>
  <Card title="Protect frontend routes" href="/additional-verification/session-verification/protect-frontend-routes" />
  <Card title="Claim validation" href="/additional-verification/session-verification/claim-validation" />
  <Card title="WebSockets authentication" href="/additional-verification/session-verification/with-websocket" />
  <Card title="Access session data" href="/post-authentication/session-management/access-session-data" />
</CardGroup>
