Skip to main content

NestJS Integration Guide

Integrating SuperTokens into a NestJS backend is a bit different than the quick setup guide shows. We will add a few things:

  • A module to house all authorization-related code
  • A service to initialize the SDK
  • A middleware to add the authorization endpoints
  • A global error handler to pass SuperTokens related errors to the SDK
  • A guard to protect your API endpoints
  • A parameter decorator to access the session in your code

We will cover each of these in the following few sections. Then, you can do the rest of the customizations by following the "Auth Flow Customizations" section.

Please look here to see how to get started with your NestJS backend.

Installing SuperTokens#

npm i -s supertokens-node

Adding a new module#

You can scaffold a module using the nest CLI by running this in the root folder of the application:

nest g module auth

The result should be a new auth folder with auth.module.ts in it.

Add config type and injection token#

Create a config.interface.ts in the auth folder. We will put the type and injection token for the SuperTokens configuration here.

./src/auth/config.interface.ts
import { AppInfo } from 'supertokens-node/types';

export const ConfigInjectionToken = 'ConfigInjectionToken';

export type AuthModuleConfig = {
appInfo: AppInfo;
connectionURI: string;
apiKey?: string;
}

Configure the Auth module#

In auth.module.ts, add the following authorization-related code:

./src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { SupertokensService } from './supertokens/supertokens.service';
import { MiddlewareConsumer, NestModule, DynamicModule } from '@nestjs/common';
import { AuthMiddleware } from './auth.middleware';
import { ConfigInjectionToken, AuthModuleConfig } from './config.interface';

@Module({
providers: [],
exports: [],
controllers: [],
})
export class AuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}

static forRoot({
connectionURI,
apiKey,
appInfo,
}: AuthModuleConfig): DynamicModule {
return {
providers: [
{
useValue: {
appInfo,
connectionURI,
apiKey,
},
provide: ConfigInjectionToken,
},
SupertokensService,
],
exports: [],
imports: [],
module: AuthModule,
};
}
}

In the provided code sample, we convert AuthModule to a dynamic module so we can configure parts of the SuperTokens setup within the App module. This approach allows for centralized configuration, which can be particularly beneficial when managing settings such as using distinct connection URIs for different environments, such as testing or production.

info

The middleware is registered using the configure method in the AuthModule class.

Adding the module to the application#

Update the app module to use the dynamic module returned by the forRoot method of the AuthModule instead of directly importing the class itself.

./src/app.module.ts
// ...
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';

@Module({
imports: [
AuthModule.forRoot({
connectionURI: "",
apiKey: "",
appInfo: {
// Learn more about this on https://supertokens.com/docs/thirdparty/appinfo
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth"
},
}),
],
controllers: [/* ... */],
providers: [/* ... */],
})
export class AppModule {}

Adding a service#

Run the following Nest CLI command from the root to scaffold a service:

nest g service supertokens auth

In the newly created supertokens.service.ts file, initialize the SDK so that you can access the injected services in event handlers:

./src/auth/supertokens/supertokens.service.ts
import { Inject, Injectable } from '@nestjs/common';
import supertokens from "supertokens-node";
import Session from 'supertokens-node/recipe/session';
import ThirdParty from 'supertokens-node/recipe/thirdparty';

import { ConfigInjectionToken, AuthModuleConfig } from "../config.interface";

@Injectable()
export class SupertokensService {
constructor(@Inject(ConfigInjectionToken) private config: AuthModuleConfig) {
supertokens.init({
appInfo: config.appInfo,
supertokens: {
connectionURI: config.connectionURI,
apiKey: config.apiKey,
},
recipeList: [
ThirdParty.init({
signInAndUpFeature: {
// We have provided you with development keys which you can use for testing.
// IMPORTANT: Please replace them with your own OAuth keys for production use.
providers: [{
config: {
thirdPartyId: "google",
clients: [{
clientId: "1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com",
clientSecret: "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"
}]
}
}, {
config: {
thirdPartyId: "github",
clients: [{
clientId: "467101b197249757c71f",
clientSecret: "e97051221f4b6426e8fe8d51486396703012f5bd"
}]
}
}, {
config: {
thirdPartyId: "apple",
clients: [{
clientId: "4398792-io.supertokens.example.service",
additionalConfig: {
keyId: "7M48Y4RYDL",
privateKey:
"-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgu8gXs+XYkqXD6Ala9Sf/iJXzhbwcoG5dMh1OonpdJUmgCgYIKoZIzj0DAQehRANCAASfrvlFbFCYqn3I2zeknYXLwtH30JuOKestDbSfZYxZNMqhF/OzdZFTV0zc5u5s3eN+oCWbnvl0hM+9IW0UlkdA\n-----END PRIVATE KEY-----",
teamId: "YWQCXGJRJL",
}
}]
}
}],
}
}),
Session.init(),
]
});
}
}

When you want to generate your own keys, please refer to the corresponding documentation to get your client ids and client secrets for each of the below providers:

Google
  • Generate your client ID and secret by following the docs here
  • Set the authorisation callback URL to <YOUR_WEBSITE_DOMAIN>/auth/callback/google
Github
  • Generate your client ID and secret by following the docs here
  • Set the authorisation callback URL to <YOUR_WEBSITE_DOMAIN>/auth/callback/github
Facebook
  • Generate your client ID and secret by following the docs here
  • Set the authorisation callback URL to <YOUR_WEBSITE_DOMAIN>/auth/callback/facebook
Note

Make sure to enable https to be able to use the test users of the Facebook app. On http://localhost, the login flow can be verified only with the app's admin user.

Apple
  • Generate your client ID and secret by following this article
  • Set the authorisation callback URL to <YOUR_API_DOMAIN>/auth/callback/apple. Note that Apple doesn't allow localhost in the URL. So if you are in dev mode, you can use the dev keys we have provided above.

Exposing SuperTokens APIs using its middleware#

The middleware file#

Scaffold the middleware by running nest g middleware auth in the application's root folder. The result should be in the auth module, called auth.middleware.ts.

Next, edit the file to use the middleware from SuperTokens:

./src/auth/auth.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { middleware } from 'supertokens-node/framework/express';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
supertokensMiddleware: any;

constructor() {
this.supertokensMiddleware = middleware();
}

use(req: Request, res: any, next: () => void) {
return this.supertokensMiddleware(req, res, next);
}
}

Update CORS settings#

Enable and update your CORS settings in main.ts:

./src/main.ts
import { NestFactory } from '@nestjs/core';
import supertokens from 'supertokens-node';
import { AppModule } from './app.module';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: ['<YOUR_WEBSITE_DOMAIN>'],
allowedHeaders: ['content-type', ...supertokens.getAllCORSHeaders()],
credentials: true,
});

await app.listen(3000);
}

bootstrap()

Add the SuperTokens error handler#

We will add the SuperTokens error handler through a NestJS exception filter.

Exception filter#

You can scaffold the exception filter using the CLI by running: nest g filter auth. This will result in a new auth.filter.ts file next to auth.module.ts. Edit this file to add the error handler:

./src/auth/auth.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { Request, Response, NextFunction, ErrorRequestHandler } from 'express';

import { errorHandler } from 'supertokens-node/framework/express';
import { Error as STError } from 'supertokens-node';

@Catch(STError)
export class SupertokensExceptionFilter implements ExceptionFilter {
handler: ErrorRequestHandler;

constructor() {
this.handler = errorHandler();
}

catch(exception: Error, host: ArgumentsHost) {
const ctx = host.switchToHttp();

const resp = ctx.getResponse<Response>();

this.handler(
exception,
ctx.getRequest<Request>(),
resp,
ctx.getNext<NextFunction>(),
);
}
}

Registering the filter#

We need to add this filter as a global exception filter. You can do this in main.ts, right after the updated CORS settings.

./src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

import supertokens from 'supertokens-node';
import { SupertokensExceptionFilter } from './auth/auth.filter';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: ['<YOUR_WEBSITE_DOMAIN>'],
allowedHeaders: ['content-type', ...supertokens.getAllCORSHeaders()],
credentials: true,
});

app.useGlobalFilters(new SupertokensExceptionFilter());

await app.listen(3000);
}

bootstrap();

Add a session verification guard#

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.

Now that the library is set up, you can add a guard to protect your API. You can scaffold this by running: nest g guard auth.

In the newly created auth.guard.ts file, implement session verification:

./src/auth/auth.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { getSession, VerifySessionOptions } from 'supertokens-node/recipe/session';

@Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly getSessionOptions?: VerifySessionOptions) {}

public async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = context.switchToHttp();

const req = ctx.getRequest();
const resp = ctx.getResponse();

// If the session doesn't exist and {sessionRequired: true} is passed to the AuthGuard constructor (default is true),
// getSession will throw an error, that will be handled by the exception filter, returning a 401 response.

// To avoid an error when the session doesn't exist, pass {sessionRequired: false} to the AuthGuard constructor.
// In this case, req.session will be undefined if the session doesn't exist.
const session = await getSession(req, resp, this.getSessionOptions);
req.session = session;
return true;
}
}

Add a parameter decorator#

Now you can add a parameter decorator to access the already verified session in your APIs. You can generate an empty decorator by running nest g decorator session auth. Edit session.decorator.ts to return the session attached to the request:

./src/auth/session/session.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const Session = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.session;
},
);

Combine the decorator and the guard to authenticate users#

You can add a protected method into a controller (e.g., app.controller.ts) that receives the verified session as a parameter. For example:

./src/app.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
// ...
import { SessionContainer } from "supertokens-node/recipe/session";
import { AuthGuard } from './auth/auth.guard';
import { Session } from './auth/session/session.decorator';
// ...

@Controller()
export class AppController {
// ...
// Test endpoint for session verification; not part of the Supertokens setup.
@Get('/test')
@UseGuards(new AuthGuard())
getSessionInfo(
@Session() session: SessionContainer,
): Record<string, unknown> {
return {
sessionHandle: session.getHandle(),
userId: session.getUserId(),
accessTokenPayload: session.getAccessTokenPayload(),
};
}
}

You should look at the "Session Management" section to see how you can use the session object.

Setup the SuperTokens core#

YesNo