This package contains security utilities for NestJS projects, for both REST and GraphQL API gateways. It supports both express and fastify.
yarn add @moveaxlab/nestjs-security
For web applications you can rely on cookies.
Include the SecurityModule
inside your application:
import { SecurityModule } from '@moveaxlab/nestjs-security';
@Module({
imports: [
SecurityModule.forRoot({
type: "cookie",
cookieDomain: "localhost", // the domain of your app
cookieExpirationMilliseconds: 15 * 60 * 1000,
jwtSecret: "secret",
// the name of your cookies
accessTokenCookieName: "access_token",
opaqueTokenCookieName: "opaque_token",
refreshTokenCookieName: "refresh_token",
})
]
})
export class AppModule;
Remember to enable cookie support for your application.
When using cookies, you can replace the access token with an opaque token if your access token may be too big for HTTP headers.
To enable the opaque token, install ioredis
as a dependency,
and configure the redis
option.
The access token will be stored on the configured redis server,
and will be replaced in the cookies with a randomly generated token.
For mobile and desktop applications you can rely on authentication headers.
import { SecurityModule } from '@moveaxlab/nestjs-security';
@Module({
imports: [
SecurityModule.forRoot({
type: "header",
jwtSecret: "secret",
})
]
})
export class AppModule;
All configurations accept a tokenConverter
option to implement
custom transformations on the parsed access token.
You can authenticate users based on their role (or token type) or based on the permission.
The library assumes that all access tokens contain a tokenType
field or a permissions
array.
Authentication can be applied on the class level or on the method level.
The library will check that the token type is equal with one of the roles declared in the decorator
import { Authenticated } from "@moveaxlab/nestjs-security";
@Authenticated("admin", "user")
class MyController {
async firstMethod() {
// accessible to both admins and users
}
@Authenticated("admin")
async secondMethod() {
// only accessible to admins
}
}
In order check that the user has a valid accessToken, but without any required permission or roles you can use the @Authenticated
decorator without any tokenType.
import { HasPermission } from "@moveaxlab/nestjs-security";
import { Authenticated } from "./authenticated.decorator";
@Authenticated()
class MyController {
async getMyProfile() {
// only accessibile to authenticated user
}
}
The library will search for the required permission in the permissions
array.
import { HasPermission } from "@moveaxlab/nestjs-security";
@HasPermission("myResource.read")
class MyController {
async firstMethod() {
// accessible to token with permission myResource.read
}
@HasPermission("myResource.write")
async secondMethod() {
// only accessible to token with the permissions myResourse.write
}
}
Use the CookieService
to set and unset the access token and refresh token.
When using express:
import { CookieService } from "@movexlab/nestjs-security";
import { Request, Response } from "express";
class Controller {
constructor(private readonly cookieService: CookieService) {}
async login(@Res({ passthrough: true }) res: Response) {
await this.cookieService.setCookies(res, accessToken, refreshToken);
}
async logout(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
await this.cookieService.clearCookies(req, res);
}
}
When using fastify:
import { CookieService } from "@movexlab/nestjs-security";
import { FastifyRequest, FastifyReply } from "fastify";
class Controller {
constructor(private readonly cookieService: CookieService) {}
async login(@Res({ passthrough: true }) res: FastifyReply) {
await this.cookieService.setCookies(res, accessToken, refreshToken);
}
async logout(
@Req() req: FastifyRequest,
@Res({ passthrough: true }) res: FastifyReply,
) {
await this.cookieService.clearCookies(req, res);
}
}
If you are using GraphQL, the request and response must be retrieved from the GraphQL context.
For express, setup your GraphQL module like this:
import { GraphQLModule } from '@nestjs/graphql';
import { Request, Response } from 'express';
@Module({
imports: [
GraphQLModule.forRoot({
// ...
context: ({ req, res }: { req: Request, res: Response }) => ({ req, res }),
})
]
})
export class AppModule;
With fastify, the setup should look like this:
import { GraphQLModule } from '@nestjs/graphql';
import { FastifyRequest, FastifyReply } from 'fastify';
@Module({
imports: [
GraphQLModule.forRoot({
// ...
context: (req: FastifyRequest, res: FastifyReply) => ({ req, res }),
})
]
})
export class AppModule;
Inside your resolvers you can access the request and response objects
using the @Context("req")
and @Context("res")
decorators.
If you are using fastify, you cannot access the response using
@Context("res")
due to a bug in@nestjs/core
. Access it instead with@Context() { res }: { res: FastifyReply }
.
You can access the access token and refresh token inside your controllers and resolvers using decorators.
import { Authenticated, AccessToken } from "@moveaxlab/nestjs-security";
@Authenticated("admin")
class MyController {
async myMethod(@AccessToken() token: string) {
// use the token here
}
}
The refresh token can be accessed via decorators when using cookies.
Include the RefreshCookieInterceptor
to retrieve it.
import {
Authenticated,
RefreshToken,
RefreshCookieInterceptor,
} from "@moveaxlab/nestjs-security";
@Authenticated("admin")
@UseInterceptors(RefreshCookieInterceptor)
class MyController {
async myMethod(@RefreshToken() token: string) {
// use the token here
}
}
You can access the parsed access token using the @User
decorator.
import { Authenticated, HasPermission, User } from "@moveaxlab/nestjs-security";
interface UserType {
tokenType: "admin" | "user";
uid: string;
permission: string[];
// other information contained in the token
}
@Authenticated("admin")
class MyController {
async myMethod(@User() token: UserType) {
// use the token here
}
}
@HasPermission("myPermission")
class MySecondController {
async mySecondMethod(@User() token: UserType) {
// use the token here
}
}
The jwtSecret
options can accept an object mapping the iss
key
contained in the token with the secret or key used to sign the token.