Skip to main content

Session verification

The following page shows you three different ways to verify sessions in a lambda integration. Choose the one that works best based on the particularities of your use case.

caution

This guide only applies to scenarios which involve SuperTokens Session Access Tokens.

If you are implementing either, Unified Login or Microservice Authentication, features that make use of OAuth2 Access Tokens, please check the separate page that shows you how to verify those types of tokens.

Using Session Verification

When building your own APIs, you may need to verify the session of the user before proceeding further. SuperTokens SDK exposes a verifySession function that can be utilized for this. In this guide, we will be creating a /user GET route that will return the current session information.

1. Add /user GET route in your API Gateway

Create a /user resource and then GET method in your API Gateway. Configure the lambda integration and CORS just like we did for the auth routes.

2. Create a file in your lambda to handle the /user route.

An example of this is here.

user.mjs
import supertokens from "supertokens-node";
import { getBackendConfig } from "./config.mjs";
import { SessionEvent } from "supertokens-node/framework/awsLambda";
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";
import middy from "@middy/core";
import cors from "@middy/http-cors";

supertokens.init(getBackendConfig());

type AuthorizerEvent = SessionEvent & APIGatewayAuthorizerEvent;

const lambdaHandler = async (event: AuthorizerEvent) => {
return {
body: JSON.stringify({
sessionHandle: event.session?.getHandle(),
userId: event.session?.getUserId(),
accessTokenPayload: event.session?.getAccessTokenPayload(),
}),
statusCode: 200,
};
};

export const handler = middy(verifySession(lambdaHandler))
.use(
cors({
origin: getBackendConfig().appInfo.websiteDomain,
credentials: true,
headers: ["Content-Type", ...supertokens.getAllCORSHeaders()].join(", "),
methods: "OPTIONS,POST,GET,PUT,DELETE",
})
)
.onError((request) => {
throw request.error;
});

Now, import this function in your index.mjs handler file as shown below:

index.mjs
import supertokens from "supertokens-node";
import { middleware } from "supertokens-node/framework/awsLambda";
import { getBackendConfig } from "./config.mjs";
import middy from "@middy/core";
import cors from "@middy/http-cors";
import { handler as userHandler } from "./user.mjs";

supertokens.init(getBackendConfig());

export const handler = middy(
middleware((event) => {

if (event.path === "/user") {
return userHandler(event);
}

return {
body: JSON.stringify({
msg: "Hello!",
}),
statusCode: 200,
};
})
)
.use(
cors({
origin: getBackendConfig().appInfo.websiteDomain,
credentials: true,
headers: ["Content-Type", ...supertokens.getAllCORSHeaders()].join(", "),
methods: "OPTIONS,POST,GET,PUT,DELETE",
})
)
.onError((request) => {
throw request.error;
});
note

The verifySession middleware automatically returns a 401 Unauthorised error if the session is not valid. You can alter the default behaviour by passing { sessionRequired: false } as the second argument to the verifySession middleware.

If each API route has its own lambda function, you can skip using the SuperTokens auth middleware. Instead, ensure to call supertokens.init and include the Session recipe in the recipeList for each respective lambda function.

Using Lambda Authorizers

You can use a lambda as an authorizer in API Gateways. This will enable you to use SuperTokens in a lambda to authorize requests to other integrations (e.g., AppSync). An Authorizer pointed to this lambda will add context.authorizer.principalId that you can map to a header. For example, you can map this to an "x-user-id" header which will be set to the id of the logged-in user. If there is no valid session for the request, this header won't exist.

1. Add configurations and dependencies

Refer to the frontend, lambda layer, and lambda setup.

2. Add code to the lambda function handler

Use the code below as the handler for the lambda. Remember that whenever we want to use any functions from the supertokens-node lib, we have to call the supertokens.init function at the top of that serverless function file. We can then use getSession() to get the session.

index.mjs
import supertokens from "supertokens-node";
import { SessionEvent } from "supertokens-node/framework/awsLambda";
import Session from "supertokens-node/recipe/session";

import { getBackendConfig } from "./config.mjs";

supertokens.init(getBackendConfig());

type AuthorizerEvent = SessionEvent & APIGatewayAuthorizerEvent;

export const handler = async function (event: AuthorizerEvent) {
try {
const session = await Session.getSession(event, event, { sessionRequired: false });
if (session) {
return generateAllow(session.getUserId(), event.methodArn, {
setCookie: event.supertokens.response.cookies.join(', '),
});
} else {
return generateAllow("", event.methodArn, {
setCookie: event.supertokens.response.cookies.join(', '),
});
}
} catch (ex: any) {
if (ex.type === "TRY_REFRESH_TOKEN" || ex.type === "UNAUTHORISED") {
throw new Error("Unauthorized");
}
if (ex.type === "INVALID_CLAIMS") {
return generateDeny("", event.methodArn, {
body: JSON.stringify({
message: "invalid claim",
claimValidationErrors: ex.payload,
}),
setCookie: event.supertokens.response.cookies.join(", "),
});
}
throw ex;
}
}

const generatePolicy = function (principalId: string, effect: StatementEffect, resource: string, context?: any) {
const policyDocument: PolicyDocument = {
Version: '2012-10-17',
Statement: [],
};

const statementOne: Statement = {
Action: 'execute-api:Invoke',
Effect: effect,
Resource: resource,
};

policyDocument.Statement[0] = statementOne;

const authResponse: AuthResponse = {
principalId: principalId,
policyDocument: policyDocument,
context,
};

return authResponse;
}

const generateAllow = function (principalId: string, resource: string, context?: any) {
return generatePolicy(principalId, 'Allow', resource, context);
};

const generateDeny = function (principalId: string, resource: string, context?: any) {
return generatePolicy(principalId, 'Deny', resource, context);
};

3. Configure the Authorizer

  • Go to the Authorizers tab in the API Gateway configuration
  • Click Create new Authorizer and add it
    • Fill the name field
    • Set "Lambda function" to the one created above
    • Set "Lambda Event Payload" to Request
    • Delete the empty "Identity Source"
    • Click "Create"

4. Configure API Gateway

  • In your API Gateway, create the resources and methods you require, enabling CORS if necessary (see setup API gateway for details)
  • Select each method you want to enable the Authorizer and configure it to use the new Authorizer
    • Click on "Method Request"

    • Edit the "Authorization" field in Settings and set it to the one we just created.

    • Go back to the method configuration and click on "Integration Request"

      • Set up the integration you require (see AppSync for an example)
      • Add a header mapping to make use of the context set in the lambda.
        • Open "HTTP Headers"
        • Add all headers required (e.g., "x-user-id" mapped to "context.authorizer.principalId")
        • Repeat for any values from the context you want to add as a Header
    • Go back to the method configuration and click on "Method Response"

      • Open the dropdown next to the 200 status code
      • Add the "Set-Cookie" header
      • Add any other headers that should be present on the response.
    • Go back to the method configuration and click on "Integration Response"

      • Open the dropdown
      • Open "Header Mappings"
      • Add "Set-Cookie" mapped to "context.authorizer.setCookie"
  • In the API Gateway left menu, select "Gateway Responses"
    • Select "Access Denied"
      • Click "Edit"
      • Add response headers:
        • Add Access-Control-Allow-Origin with value '<YOUR_WEBSITE_DOMAIN>'
        • Add Access-Control-Allow-Credentials with value 'true'. Don't miss out on those quotes else it won't get configured correctly.
        • Add "Set-Cookie" with value context.authorizer.setCookie no quotes
      • Under response templates:
        • Select application/json:
        • Set "Response template body" to $context.authorizer.body
      • Click "Save"
    • Select "Unauthorized"
      • Add response headers:
        • Add Access-Control-Allow-Origin with value '<YOUR_WEBSITE_DOMAIN>'
        • Add Access-Control-Allow-Credentials with value 'true'. Don't miss out on those quotes else it won't get configured correctly.
      • Click "Save"
  • Deploy your API and test it

Using JWT Authorizers

caution

AWS supports JWT authorizers for HTTP APIs and not REST APIs on the API Gateway service. For REST APIs follow the Lambda authorizer guide

This guide will work if you are using SuperTokens Session Tokens.

If you implementing an OAuth2 setup, through the Unified Login or the Microservice Authentication features, you will have to manually set the token audience property. Please check the referenced pages for more information.

1. Add the aud claim in the JWT based on the authorizer configuration

App Info

Adjust these values based on the application that you are trying to configure. To learn more about what each field means check the references page.
This is the URL of your app's API server.
This is the URL of your app's API server.
SuperTokens will expose it's APIs scoped by this base API path.
This is the URL of your website.
The path where the login UI will be rendered
config.mjs

import Session from 'supertokens-node/recipe/session'

export function getBackendConfig() {
return {
framework: "awsLambda",
supertokens: {
connectionURI: "<CORE_API_ENDPOINT>",
apiKey: "<YOUR_API_KEY>",
},
appInfo: {
// learn more about this on https://supertokens.com/docs/session/appinfo
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth",
},
recipeList: [
Session.init({
exposeAccessTokenToFrontendInCookieBasedAuth: true,
override: {
functions: function (originalImplementation) {
return {
...originalImplementation,
createNewSession: async function (input) {
input.accessTokenPayload = {
...input.accessTokenPayload,
/*
* AWS requires JWTs to contain an audience (aud) claim
* The value for this claim should be the same
* as the value you set when creating the
* authorizer
*/
aud: "jwtAuthorizers",
};

return originalImplementation.createNewSession(input);
},
};
}
},
}),
],
isInServerlessEnv: true,
}
}

2. Configure your authorizer

  • Go to the "Authorizers" tab in the API Gateway configuration and select the "Manage authorizers" tab
  • Click "Create", in the creation screen select "JWT" as the "Authorizer type"
  • Enter a name for your authorizer (You can enter any name for this field)
  • Use $request.header.Authorization for the "Identity source". This means that API requests will contain the JWT as a Bearer token under the request header "Authorization".
  • Use <YOUR_API_DOMAIN>/<apiGatewayPath>//auth for the "Issuer URL".
  • Set a value for the "Audience" field, this will be the value you expect the JWT to have under the aud claim. In the backend config above the value is set to "jwtAuthorizers"

3. Add the authorizer to your API

  • In the "Authorization" section select the "Attach authorizers to routes" tab
  • Click on the route you want to add the authorizer to and select the authorizer you created from the dropdown
  • Click "Attach authorizer"
  • Deploy your changes and test your API

4. Check for auth claims of the JWT

Once the JWT authorizer successfully validates the JWT, the claims of the JWT will be available to your lambda functions via $event.requestContext.authorizer.jwt.claims. You should check for the right authorization access here. For example, if one of your lambda functions requires that the user's email is verified, then it should check for the jwt payload's st-ev claim value to be {v: true, t:...}, else it should reject the request. Similar checks need to be done to enforce the right user role or if 2FA is completed or not. This is required because SuperTokens issues JWTs immediately after the user signs up / logs in, regardless of if all the authorisation checks pass or not. Functions exposed by our SDK like verifySession or getSession do these authorisation checks on their own, but since these functions are not used in the this flow, you will have to check them on your own.