Access Token Blacklisting
By default, session verification is stateless. This means that SuperTokens does not check that the session actually exists in the database, and only verifies the session by checking it's signature. Whilst this makes session verifications fast, it also means that if a session is revoked, the user will still be able to use it until the access token expires.
If you want session verifications to fail immediately after the session has revoked, you should use this feature. Since you can use this feature on a per API basis, we recommend that you only use it for non GET APIs since only those are state changing.
This feature works by passing the checkDatabase
option when verifying the session as shown below.
caution
For managed service users, please check our rate limit policy before implementing this feature. If you suspect that you will breach the free limit you can:
- Email us to increase your rate limit.
- Use the
checkDatabase
flag only on certain important APIs. For example, omit using it in anyGET
API as those are not state changing. - Implement your own method for keeping track of revoked access tokens by using a cache like Redis.
verifySession
middleware#
Using the - NodeJS
- GoLang
- Python
- Other Frameworks
Important
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js (Pages Dir)
- Next.js (App Dir)
- NestJS
import express from "express";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";
let app = express();
app.post("/like-comment", verifySession({ checkDatabase: true }), (req: SessionRequest, res) => {
let userId = req.session!.getUserId();
//....
});
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({ checkDatabase: true })
},
],
},
handler: async (req: SessionRequest, res) => {
let userId = req.session!.getUserId();
//...
}
})
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({ checkDatabase: true }),
}, (req: SessionRequest, res) => {
let userId = req.session!.getUserId();
//....
});
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, { checkDatabase: true });
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({ checkDatabase: true }), (ctx: SessionContext, next) => {
let userId = ctx.session!.getUserId();
//....
});
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({ checkDatabase: true }))
@response(200)
handler() {
let userId = (this.ctx as SessionContext).session!.getUserId();
//....
}
}
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({ checkDatabase: true })(req, res, next);
},
req,
res
)
let userId = req.session!.getUserId();
//....
}
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({})
}, { checkDatabase: true });
}
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({ checkDatabase: true })) // For more information about this guard please read our NestJS guide.
async postExample(@Session() session: SessionContainer): Promise<boolean> {
let userId = session.getUserId();
//....
return true;
}
}
- Chi
- net/http
- Gin
- Mux
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
checkDb := true
session.VerifySession(&sessmodels.VerifySessionOptions{
CheckDatabase: &checkDb,
}, 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)
}
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
checkDb := true
router.POST("/likecomment", verifySession(&sessmodels.VerifySessionOptions{
CheckDatabase: &checkDb,
}), 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)
}
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
checkDb := true
r.Post("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{
CheckDatabase: &checkDb,
}, 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)
}
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
checkDb := true
router.HandleFunc("/likecomment", session.VerifySession(&sessmodels.VerifySessionOptions{
CheckDatabase: &checkDb,
}, 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)
}
- FastAPI
- Flask
- Django
from supertokens_python.recipe.session.framework.fastapi import verify_session
from supertokens_python.recipe.session import SessionContainer
from fastapi import Depends
@app.post('/like_comment')
async def like_comment(session: SessionContainer = Depends(verify_session(check_database=True))):
user_id = session.get_user_id()
print(user_id)
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.session import SessionContainer
from flask import g
@app.route('/update-jwt', methods=['POST'])
@verify_session(check_database=True)
def like_comment():
session: SessionContainer = g.supertokens
user_id = session.get_user_id()
print(user_id)
from supertokens_python.recipe.session.framework.django.asyncio import verify_session
from django.http import HttpRequest
from supertokens_python.recipe.session import SessionContainer
@verify_session(check_database=True)
async def like_comment(request: HttpRequest):
session: SessionContainer = request.supertokens
user_id = session.get_user_id()
print(user_id)
getSession
#
Using - NodeJS
- GoLang
- Python
- Other Frameworks
Important
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js (Pages Dir)
- Next.js (App Dir)
- NestJS
import express from "express";
import 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, { checkDatabase: true })
if (session !== undefined) {
let userId = session.getUserId();
} else {
// user is not logged in...
}
//....
} catch (err) {
next(err);
}
});
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, { checkDatabase: true })
if (session !== undefined) {
let userId = session.getUserId();
} else {
// user is not logged in...
}
//...
}
})
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, { checkDatabase: true })
if (session !== undefined) {
let userId = session.getUserId();
} else {
// user is not logged in...
}
//....
});
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, { checkDatabase: true })
if (session !== undefined) {
let userId = session.getUserId();
} else {
// user is not logged in...
}
//....
};
exports.handler = middleware(likeComment);
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, { checkDatabase: true })
if (session !== undefined) {
let userId = session.getUserId();
} else {
// user is not logged in...
}
//....
});
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, { checkDatabase: true })
if (session !== undefined) {
let userId = session.getUserId();
} else {
// user is not logged in...
}
//....
}
}
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, { checkDatabase: true });
},
req,
res
)
if (session !== undefined) {
let userId = session.getUserId();
} else {
// user is not logged in...
}
//....
}
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({})
}, { checkDatabase: true });
}
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, { checkDatabase: true })
if (session !== undefined) {
const userId = session.getUserId();
} else {
// user is not logged in...
}
//....
return true;
}
}
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) {
checkDb := true
sessionContainer, err := session.GetSession(r, w, &sessmodels.VerifySessionOptions{
CheckDatabase: &checkDb,
})
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
}
}
- FastAPI
- Flask
- Django
from supertokens_python.recipe.session.asyncio import get_session
from fastapi import Request
@app.post('/like-comment')
async def like_comment(request: Request):
session = await get_session(request, check_database=True)
if session is not None:
user_id = session.get_user_id()
print(user_id) # TODO:
else:
pass # user is not logged in
from supertokens_python.recipe.session.syncio import get_session
from flask.wrappers import Request
@app.route('/like-comment', methods=['POST'])
def like_comment(request: Request):
session = get_session(request, check_database=True)
if session is not None:
user_id = session.get_user_id()
print(user_id) # TODO..
else:
pass # user is not logged in
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, check_database=True)
if session is not None:
user_id = session.get_user_id()
print(user_id) # TODO..
else:
pass # user is not logged in