Skip to main content

Session Verification using getSession

If you want to use a non-middleware form of verifySession, you can use the getSession function.

Using getSession#

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

if (session === undefined) {
throw Error("Should never come here")
}

let userId = session.getUserId();
//....
} catch (err) {
next(err);
}
});

The session object#

This object exposes the following functions:

  • getHandle: Returns the sessionHandle for this session. This is a constant, unique string per session that never changes for its session.
  • getUserId: Returns the userId of logged in user
  • getSessionDataFromDatabase: Returns the session data (stored in the database) that is associated with the session.
  • updateSessionDataInDatabase: Set a new JSON object to the session data (stored in the database)
  • getAccessTokenPayload: Returns the access token's payload for this session. This includes claims defined by you (e.g.: in createNewSession), standard claims (sub, iat, exp) and supertokens specific ones (sessionHandle, parentRefreshTokenHash1, etc.)
  • mergeIntoAccessTokenPayload: Adds key / values into a JSON object in the access token. Set a key to null to remove it from the payload.
  • revokeSession: Destroys this session in the db and on the frontend
  • getTimeCreated: Returns the time in milliseconds of when this session was created
  • getExpiry: Returns the time in milliseconds of when this session will expire if not refreshed.
  • getAccessToken: Returns the raw string access token
  • getAllSessionTokensDangerously: Returns an object that contains the raw string representation of all tokens associated with the session along with a boolean that indicates if thee session needs to be updated on the frontend.

getSessionDataFromDatabase vs getAccessTokenPayload#

  • getSessionDataFromDatabase queries the SuperTokens Core to get the information, mapped to that session's handle, from the database. Whereas getAccessTokenPayload reads directly from the access token used in the request.
  • getSessionDataFromDatabase is much slower since it requires a network call.
  • The information stored using updateSessionDataInDatabase (changes the result of getSessionDataFromDatabase function call), is not exposed to the frontend in any way. So if you want to store something sensitive against the session handle, use this method.
  • If you want access to some information in most / all API, like the user's role, then use getAccessTokenPayload and mergeIntoAccessTokenPayload since fetching this information from the session will be very fast (no network call required).

Optional session verification#

Sometimes, you want an API to be accessible even if there is no session. In that case, you can use the sessionRequired flag:

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);
}
});

Verifying the claims of a session#

Sometimes, you may also want to check if there are certain claims in the session as part of the verification process. For example, you may want to check that the session has the admin role claim for certain APIs, or that the user has completed 2FA.

This can be done using our session claims validator feature. Let's take an example of using the user roles claim to check if the session has the admin claim:

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")
]
});

if (session === undefined) {
throw Error("Should never come here")
}

let userId = session.getUserId();
//....
} catch (err) {
next(err)
}
});
  • We add the UserRoleClaim validator to the verifySession function which makes sure that the user has an admin role.
  • The globalValidators represents other validators that apply to all API routes by default. This may include a validator that enforces that the user's email is verified (if enabled by you).
  • We can also add a PermissionClaim validator to enforce a permission.
feature

You can also build your own custom claim validators based on your app's requirements.

Building your own custom middleware#

Both these functions do session verification. However, verifySession is a middleware that returns a reply directly to the frontend if the input access token is invalid or expired. On the other hand, getSession is a function which returns a session object on successful verification, and throws an exception which can be handled by you in case the access token is expired or is invalid.

Internally, verifySession uses getSession in the following way:

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.TOKEN_THEFT_DETECTED) {
// Session hijacking attempted. You should revoke the session
// using Session.revokeSession fucntion and send a 401
} 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
}

// 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)
}
}
};
}

The errorHandler will send 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.

Using getSession without req / res objects#

In the above snippets, we see that getSession requires the request object and depending on the your backend language and framework, may also require the response object. Either way, this version of getSession automatically reads from the request cookies / headers and also automatically sets the response cookies / headers 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 in a very similar way 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, and your responsibility to write the update tokens to the response (if the tokens are even updated during this API call).

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.TOKEN_THEFT_DETECTED) {
// Session hijacking attempted. You should revoke the session
// using Session.revokeSession fucntion and send a 401
} 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
}
}
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
}
}
}
}