Skip to content

A NestJS library to handle JWT authentication for web and mobile apps.

License

Notifications You must be signed in to change notification settings

moveaxlab/nestjs-security

Repository files navigation

NestJS Security

NPM npm

This package contains security utilities for NestJS projects, for both REST and GraphQL API gateways. It supports both express and fastify.

Installation

yarn add @moveaxlab/nestjs-security

Setup with cookies

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.

Setup with headers

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;

Custom token conversion logic

All configurations accept a tokenConverter option to implement custom transformations on the parsed access token.

Authenticating users

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.

@Authenticated

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

@HasPermission

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

Setting cookies

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

Using GraphQL

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 }.

Getting the tokens inside a controller or resolver

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

Using different secrets based on the issuer

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.

About

A NestJS library to handle JWT authentication for web and mobile apps.

Topics

Resources

License

Stars

Watchers

Forks