Blacklist access tokens
Enable immediate session revocation by implementing access token blacklisting with database checks.
Overview
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 its signature. Although this makes session verification fast, it also means that if you revoke a session, the user can still use its access token until the token expires.
If verification must fail immediately after session revocation, force an authoritative database check.
You can enable this per API. Choose endpoints based on the sensitivity of the data or action and your threat model, not
the HTTP method. A confidential GET endpoint can require immediate revocation just as much as a state-changing endpoint.
Before you start
Using Verify Session
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 { 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;
}
}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)
}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(check_database=True))):
user_id = session.get_user_id()
print(user_id)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(check_database=True)
def like_comment():
session: SessionContainer = g.supertokens
user_id = session.get_user_id()
print(user_id)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(check_database=True)
async def like_comment(request: HttpRequest):
session: SessionContainer = cast(SessionContainer, request.supertokens)
user_id = session.get_user_id()
print(user_id)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 },
);
}Using Get Session
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 { 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
}
}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, check_database=True)
if session is not None:
user_id = session.get_user_id()
print(user_id) # TODO:
else:
pass # user is not logged infrom flask.wrappers import Request
from supertokens_python.recipe.session.syncio import get_session
@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 infrom 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 inimport { 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 },
);
}