How to Handle Authentication and Authorization in NestJS

Updated: December 31, 2023 By: Guest Contributor Post a comment

Introduction

Handling authentication and authorization is a critical component in modern web applications. This tutorial dives into best practices for managing these security features within a NestJS application, with the latest TypeScript syntax in play.

Setting Up Your NestJS Project

Before diving into authentication and authorization, initialize your NestJS project by running:

nest new project-name

After creating the project, navigate to its directory:

cd project-name

Implementing Authentication with Passport

NestJS leverages Passport.js for authentication. First, install the required packages:

npm install @nestjs/passport passport passport-local
npm install @types/passport-local --save-dev

Create an auth module, service, and a local strategy:

nest generate module auth
nest generate service auth
nest generate strategy auth/local

In your local strategy:

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';

class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super();
  }

  async validate(username: string, password: string): Promise {
    // Add your validation logic here
  }
}

For handling user validation, you should implement AuthService:

import { Injectable } from '@nestjs/common';

@Injectable()
export class AuthService {
  // Your user validation logic goes here
}

Setting Up JWT for Sessions

To manage sessions with JWT, you’ll need additional packages:

npm install @nestjs/jwt passport-jwt
npm install @types/passport-jwt --save-dev

Implement a JWT strategy similar to the local strategy, using passport-jwt.

Securing Routes with Guards

Authentication isn’t useful without securing routes. Implement a guard to ensure users are authenticated:

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

Then, apply the guard to your routes using decorators:

import { Controller, Request, Post, UseGuards } from '@nestjs/common';

@Controller()
export class AppController {
  @UseGuards(LocalAuthGuard)
  @Post('auth/login')
  async login(@Request() req) {
    return req.user;
  }
  // ... additional methods ... 
}

Implementing Authorization with Roles

Authorization ensures that authenticated users have permissions to perform certain actions. NestJS supports role-based access control:

import { Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { CanActivate, ExecutionContext } from '@nestjs/common';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!requiredRoles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return requiredRoles.some((role) => user.roles?.includes(role));
  }
}

To associate roles with routes, create a custom decorator:

import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

Then, secure a route with the roles decorator and RolesGuard:

@Roles('admin')
@UseGuards(RolesGuard)
async adminAction() {
  // admin only action
}

In advanced scenarios, you may need to refresh tokens. Provide an endpoint for refresh token generation and a strategy to handle it.

Conclusion

Addressing authentication and authorization in NestJS is fundamental for creating secure applications. This tutorial guides you through setting up basic to advanced strategies within the NestJS framework, equipping you to effectively manage your application’s security with confidence. Always test your implementations and keep your dependencies current to uphold best practices.