Backend Development

Building Secure Apps: A Guide to NestJS Authentication

Level up your NestJS projects: implement robust JWT authentication with ease.

Bhavik Charola
5 min read

Building Secure Apps: A Guide to NestJS Authentication

[Imagen of a shield with a keyhole in the center, with binary code flowing through the keyhole]

Hey coders! SaaS, founder of code99.io here. Today, we're diving into the world of NestJS, a powerful framework for building scalable Node.js applications. But before you unleash the full potential of NestJS, it's crucial to secure your app with a robust authentication system.

In this blog, we'll be your guide on implementing rock-solid authentication in your NestJS projects. Here's what we'll cover:

  • The Importance of Authentication: We'll explore why authentication is a must-have for any web application and how NestJS simplifies the process.
  • Popular Authentication Strategies: We'll delve into common strategies like JWT (JSON Web Token) and Passport.js, a powerful middleware for authentication in Node.js applications.
  • Step-by-Step Implementation: Buckle up as we walk you through a step-by-step guide on setting up JWT authentication in your NestJS project. We'll cover everything from creating services to securing routes.
  • Best Practices and Security Tips: We'll share some pro tips on implementing secure authentication and common pitfalls to avoid.

By the end of this blog, you'll be equipped with the knowledge and tools to build secure and user-friendly authentication flows in your NestJS applications.

Bonus! We'll also include code snippets and resources to help you get started quickly.

So, whether you're a NestJS beginner or looking to enhance your authentication skills, this blog is for you! Let's jump right in.

Why Authentication Matters

Authentication is the foundation of any secure web application. It ensures only authorized users can access specific functionalities or data within your app. NestJS provides a modular and well-structured approach to implementing authentication, making it easier for developers to integrate robust security measures.

Popular Authentication Strategies in NestJS

Here are two popular authentication strategies you can leverage in your NestJS projects:

  • JWT (JSON Web Token): JWT is an industry-standard for authentication. It uses a compact, self-contained token to represent a user's identity. This token can be easily transmitted between the client and server, making it ideal for modern web applications.
  • Passport.js: Passport.js is a powerful middleware for Node.js authentication. It simplifies the process of implementing various authentication strategies, including JWT, local authentication (username/password), OAuth, and more. NestJS integrates seamlessly with Passport.js, allowing you to leverage its flexibility for your authentication needs.

Step-by-Step JWT Authentication in NestJS

Now, let's get our hands dirty and implement JWT authentication in a NestJS project. We'll create a basic user model, authentication service, and secure a route.

1. Project Setup:

Make sure you have Node.js and npm (or yarn) installed on your system. Run the following command to create a new NestJS project:

nest new nestjs-auth-app

2. Install Dependencies:

We'll need the @nestjs/jwt and @nestjs/passport packages for JWT and Passport.js integration, respectively. Install them using npm or yarn:

npm install @nestjs/jwt @nestjs/passport passport-jwt

3. User Model:

Create a user.model.ts file with the following content to define a simple user model:

export class User {
  id: number;
  username: string;
  password: string;
}

4. Authentication Service:

Create an auth.service.ts file and implement the following logic for user login and JWT generation:

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { User } from './user.model';

@Injectable()
export class AuthService {
  constructor(private readonly jwtService: JwtService) {}


  private async validateUser(username: string, password: string): Promise<User | null> {
    // Implement your user validation logic here (e.g., database call)
    // This example returns a dummy user for demonstration purposes only
    return username === 'admin' && password === 'changeme'; // Replace with real validation logic
  }
}

5. JWT Strategy:

Create a jwt.strategy.ts file to define a Passport strategy for JWT authentication:

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: 'your_secret_key', // Replace with a strong secret key
    });
  }

  async validate(payload: any) {
    return {username: payload.username}
  }
}

6. Auth Controller:

Create an auth.controller.ts file to handle login requests and provide a route for protected resources:

import { Controller, Post, Body, Get, UseGuards, Req } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './login.dto';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  @Public()
  async login(@Body() loginDto: LoginDto): Promise<{ token: string }> {
    return await this.authService.login(loginDto);
  }

  @Get('profile')
  getProfile(@Req() request) {
    // Access user data from request object after validation
    return { message: 'Welcome, authorized user!' };
  }
}

7. LoginDto:

Create a login.dto.ts file to define the data structure for login requests:

export class LoginDto {
  username: string;
  password: string;
}

8. JWT Auth Guard:

Create a jwt-auth.guard.ts file to implement a guard that protects routes based on JWT presence:

import {
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { IS_PUBLIC_KEY } from 'src/shared/decorator/public';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    // Add your custom authentication logic here
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) {
      return true;
    }
    // for example, call super.logIn(request) to establish a session.
    return super.canActivate(context);
  }

  handleRequest(err, user) {
    // You can throw an exception based on either "info" or "err" arguments
    if (err || !user) {
      throw err || new UnauthorizedException();
    }
    return user;
  }
}

9. Main Module:

Update your main.ts file to import necessary modules, configure providers, and apply the JWT strategy:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
import { AuthController } from './auth.controller';
import { LoginDto } from './login.dto';
import { APP_GUARD } from '@nestjs/common';
import { JwtAuthGuard } from './jwt-auth.guard';

@Module({
  imports: [
    JwtModule.register({
      secretOrKey: 'your_secret_key', // Replace with a strong secret key
      signOptions: { expiresIn: '60s' },
    }),
  ],
  providers: [
    // Use the JwtAuthGuard for guarding all routes with JWT authentication
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },
  ],
})