A Step-by-Step Guide to Implement JWT Authentication in NestJS using Passport
Tags: nestjs, coding, backend, security
Published: 2024-01-23 10:48:32

In the domain of computer security, authentication and authorization are fundamental processes. Authentication, the initial step, validates a user's identity through methods like passwords or biometrics. Once authenticated, authorization comes into play, determining the user's permissions and access rights. It's crucial to note that authentication precedes authorization, establishing the user's identity before enforcing access control measures. 

The purpose of this article is to provide a step-by-step guide for implementing authentication system in a [NestJS](https://nestjs.com/) project using the [Passport](https://www.passportjs.org/) middleware module.

Our authentication system will embody the key attributes of an exemplary system: it will be **secure**, **easily extensible**, and **simple**, as every good authentication system should be.

If you want to look at the full code you can check out [my repo on Github](https://github.com/sipios/tuto-authentication-nestjs).


## Dependencies installation

- **[passport](https://www.npmjs.com/package/passport)** (^0.7.0) is an authentication middleware for Node.js, widely used and extensible.
- **[passport-local](https://www.npmjs.com/package/passport-local)**  ( ^1.0.0) is a Passport strategy for authentication with an email and password.
- **[passport-jwt](https://www.npmjs.com/package/passport-jwt)**  (^4.0.1)is a Passport strategy for authentication with a JSON Web Token (JWT).
- **[@nestjs/passport](https://www.npmjs.com/package/@nestjs/passport)** (^10.0.3) is a Passport integration for NestJS.
- **[@nestjs/jwt](https://www.npmjs.com/package/@nestjs/jwt)**  (^10.2.0) is used to handle JWT tokens in NestJS. **[JWT (JSON Web Tokens)](https://jwt.io/introduction)** is a compact and safe way to transmit information between parties as a JSON object. this information can be verified and trusted because it is digitally signed.
- **[bcrypt](https://www.npmjs.com/package/bcrypt)** (5.1.1)is a library for hashing passwords.


```bash
npm install @nestjs/passport passport passport-local passport-jwt @nestjs/jwt bcrypt
```


> ❓ **Why you should use Passport Module to implement your authentication service**
> For implementing authentication in your NestJs App I highly recommend relying on the Passport module. It enhances security through its robust strategies like JWT and OAuth, aligning with industry best practices. Among other things, it supports a plug-and-play architecture, enabling the addition of new authentication methods or the replacement of existing ones without major code modifications. This means you can effortlessly integrate custom authentication strategies, ensuring scalability and adaptability for your app's future needs. In other words, Passport ensures a smooth developer experience, making it easy for you to understand and implement authentication mechanisms.



##User Module

Define a model (e.g., a class or an interface) for your users, including necessary fields such as `email`, `password`, etc. The [user service](https://github.com/sipios/tuto-authentication-nestjs/blob/main/src/users/users.service.ts) responsible for all interactions with the database regarding users includes methods to create, find, update, and delete users. However, notice that the signup method must be handled by the authentication service.


##Authentication Module

Your authentication module should be (at least) responsible for the following crucial tasks:

1. User Identity verification
2. Password Hashing and Storage
3. Token Generation and Validation

### Authentication Service

Create an `AuthService` service that will inject necessary services, such as `UsersService` for interacting with the user database and `JwtService` for handling JWT tokens.

**AuthService Methods**:

- **validateUser**: Takes a username and password as input and checks if the user exists and if the password is correct.
- **login**: Takes user data as input and generates a JWT token
- **register:** Checks if a user with the provided email already exists.If a user with the provided email already exists, throws a BadRequestException, if not, hash and salt the user's password using `bcrypt`, store the new user in the database and log the user in.

<script src="https://gist.github.com/SipiosMarket/e4c1146ee9d9b1d31e22cf3416243d5e.js"></script>

```jsx
//auth.service.ts
import { BadRequestException, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import { User } from 'src/users/users.entity';
import { AccessToken } from './types/AccessToken';
import { UsersService } from 'src/users/users.service';
import { RegisterRequestDto } from './dtos/register-request.dto';

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
  ) {}

  async validateUser(email: string, password: string): Promise<User> {
    const user: User = await this.usersService.findOneByEmail(email);
    if (!user) {
      throw new BadRequestException('User not found');
    }
    const isMatch: boolean = bcrypt.compareSync(password, user.password);
    if (!isMatch) {
      throw new BadRequestException('Password does not match');
    }
    return user;
  }

  async login(user: User): Promise<AccessToken> {
    const payload = { email: user.email, id: user.id };
    return { access_token: this.jwtService.sign(payload) };
  }

  async register(user: RegisterRequestDto): Promise<AccessToken> {
    const existingUser = this.usersService.findOneByEmail(user.email);
    if (existingUser) {
      throw new BadRequestException('email already exists');
    }
    const hashedPassword = await bcrypt.hash(user.password, 10);
    const newUser: User = { ...user, password: hashedPassword };
    await this.usersService.create(newUser);
    return this.login(newUser);
  }
}
```


> ❓ **Why is hashing and salting passwords mandatory?**
> A **salt** is simply a random data used as an additional input to the hashing function to safeguard your password. The random string from the salt makes the hash unpredictable.
> A password **hash** involves converting the password into an alphanumeric string using specialized algorithms.
> Hashing and salting are irreversible and ensure that even if someone gains access to the hashed passwords, they will not be able to decrypt them to recover the original passwords.
> Hystorically bcrypt is recognized as the best hashing algorithm. However, in terms of robustness against all the new cryptographic attacks targeting hashing algorithms, the current clear winner is [`argon2`](https://www.npmjs.com/package/argon2). However, since the “youth" (2015) of this algorithm, I chose to use `bcrypt`



### Passport Configuration

#### Create a Local Strategy for authentication with username and password.

You need to create a **`LocalStrategy`** that extends **`PassportStrategy`**. 

This **authentication method** is primarily used for authenticating users based on local credentials such as usernames and passwords. By default the strategy looks for a `username` field, in the code below we override **`usernameField`** to look for an `email` field since we want to login with an email.

- **validate method :** Requires the server to check the provided username and password against the authentication service.

<script src="https://gist.github.com/SipiosMarket/dda6a7ae26d44545b41df0774875fdad.js"></script>

```jsx
//local.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthenticationService } from '../authentication.service';
import { User } from '../../app/users/users.entity';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthenticationService) {
    super({
      usernameField: 'email',
    });
  }

  async validate(email: string, password: string): Promise<User> {
    const user = await this.authService.validateUser(email, password);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}
```

#### **Create a JWT Strategy to validate JWT tokens**

The `JwtStrategy` validates the token sent by the user. It extracts the user ID from the token and looks it up in the database.

This **authentication method** is used for authenticating users based on the presence and validity of a JSON Web Token (JWT). It extracts the user ID from the token and looks it up in the database.

- **validate method:** Involves checking the digital signature of the JWT to ensure its integrity and validating claims to determine the user's identity and permissions. The `validate` method receives the decoded data from the JWT token and returns user data if the token is valid. You can add additional logic, e.g., to fetch additional user information from the database.

<script src="https://gist.github.com/SipiosMarket/81903c6a3e561d02a5ca7a6a0e18e41d.js"></script>

```jsx
//jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { AccessTokenPayload } from '../types/AccessTokenPayload';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get('JWT_SECRET'),
    });
  }

  async validate(payload: AccessTokenPayload) {
    return payload;
  }
}
```

## Authentication Controller

Create endpoints for signup (`register`) and login (`login`) that both return JWT tokens. For the login endpoint, the`@UseGuards` decorator is applied to the `login` method, indicating that the `AuthGuard` named 'local' should be used, that will trigger the `LocalStrategy` previously implemented.

<script src="https://gist.github.com/SipiosMarket/13f5e5a36c472c41ebd493b02fd87644.js"></script>

```jsx
// auth.controller.ts
import {
  BadRequestException,
  Body,
  Controller,
  Post,
  Request,
  UseGuards,
} from '@nestjs/common';
import { AuthService } from './auth.service';

import { AuthGuard } from '@nestjs/passport';
import { RegisterRequestDto } from './dtos/register-request.dto';
import { LoginResponseDTO } from './dtos/login-response.dto';
import { RegisterResponseDTO } from './dtos/register-response.dto';

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

  @UseGuards(AuthGuard('local'))
  @Post('login')
  async login(@Request() req): Promise<LoginResponseDTO | BadRequestException> {
    return this.authService.login(req.user);
  }

  @Post('register')
  async register(
    @Body() registerBody: RegisterRequestDto,
  ): Promise<RegisterResponseDTO | BadRequestException> {
    return await this.authService.register(registerBody);
  }
}
```

<aside>
❓ **Passing the Access Token to the client**
In this tutorial we pass the access token to the client in the response body for simplicity reasons, but it can also be done with a **cookie**. Since cookies are automatically included in every HTTP request, it simplifies the process of including the access token without explicit action from the client. 
You can read this article [Cookie-Based Authentication vs Token-Based Authentication](https://hasanthipurnimadissanayake.medium.com/cookie-based-authentication-vs-token-based-authentication-9c0dcce1d73a) to choose the most adapted method to your case.

</aside>

## **Authentication Module Configuration**

Your `AuthModule` should import the `JwtModule` to handle JSON Web Token (JWT) generation and verification. 

The `registerAsync` method allows for asynchronous configuration, facilitating the use of the `ConfigService` to retrieve the JWT secret from environment variables stored in a `.env` file.

Storing sensitive configurations in a `.env` file as environment variables is preferred because it enhances security and avoids accidental exposure in version control systems (⚠️ don’t forget to add it in the `.gitignore` ). This practice facilitates easy configuration management across different environments while preserving confidentiality and minimizing the risk of inadvertent information disclosure.

<script src="https://gist.github.com/SipiosMarket/c0f085ac138ea44854b8cb8ba3b17f34.js"></script>

```tsx
// auth.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { LocalStrategy } from './strategy/local.strategy';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './strategy/jwt.strategy';

@Module({
  imports: [
    UsersModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get<string>('JWT_SECRET'),
        signOptions: {
          expiresIn: parseInt(
            configService.getOrThrow<string>(
              'ACCESS_TOKEN_VALIDITY_DURATION_IN_SEC',
            ),
          ),
        },
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService, JwtModule],
})
export class AuthModule {}
```

## Routes security: implementing a whitelisting strategy

> ❓ **Why should you use a whitelisting strategy ?**
> To ensure the security of your app, the best practice is to choose a **whitelisting** strategy. Whitelisting, involves explicitly allowing only approved elements or actions, providing precise control over permissions in a system. It is considered a better practice than blacklisting as it reduces the attack surface by permitting only known and necessary entities but also minimizes developers' errors resulting from oversights or thoughtlessness.

In our context, to implement whitelisting you basically just have to create a guard for JWT-based authentication, and a decorator to designate public routes exempt from JWT authentication. This strategy offers a granular and flexible approach to securing specific endpoints. 

### Create a guard and a decorator

First, create the following decorator:

<script src="https://gist.github.com/SipiosMarket/4746ada7a8987f83354b1902a130d5ae.js"></script>

```tsx
//public.decorator.ts 
import { SetMetadata } from '@nestjs/common';

export const Public = () => SetMetadata('isPublic', true);
```

Then you can add the following **`JwtGuard`** class: 

<script src="https://gist.github.com/SipiosMarket/aff09ce4d424ca3a2add6d286bd6b837.js"></script>

```tsx
//jwt.guard.ts
import { ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }
  canActivate(
    context: ExecutionContext,
  ): Promise<boolean> | boolean | Observable<boolean> {
    const isPublic = this.reflector.getAllAndOverride('isPublic', [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) return true;
    return super.canActivate(context);
  }
}
```

- **Class constructor:** takes a single parameter - an instance of the `Reflector` class. The `Reflector` class is part of the core NestJS library and is used to retrieve metadata associated with classes, methods, and their decorators.
- **canActivate Method:**
The `canActivate` method is a crucial part of the `JwtGuard` class. It is invoked during the request lifecycle to determine whether the current request should be allowed or denied based on the provided JWT.
    - The first step is to check if the route or endpoint is marked as public using the `@Public` decorator previously created.
    - The `Reflector` is used to retrieve metadata labeled as 'isPublic' from the handler and class associated with the current execution context.
    - If the route is marked as public, the method returns `true`, indicating that no further authentication checks are needed.
    - If the route is not marked as public, the `super.canActivate(context)` call invokes the corresponding method in the parent class (`AuthGuard`). This method performs the actual JWT validation and authentication checks.

### Configure the AppModule

Within the `providers` array, the provider for the JWT guard is registered using the `APP_GUARD` token. This provider specifies the use of the `JwtGuard` class as the global guard for the entire application.

<script src="https://gist.github.com/SipiosMarket/e9144a623a16ac842640c9e72e2a568c.js"></script>

```tsx
//app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
import { JwtStrategy } from './auth/strategy/jwt.strategy';
import { APP_GUARD } from '@nestjs/core';
import { JwtGuard } from './auth/guards/jwt.guard';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
		...
    UsersModule,
    AuthModule,
  ],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_GUARD,
      useClass: JwtGuard,
    },
    JwtStrategy,
  ],
})
export class AppModule {}
```

Now if the routes are not marked with `@Public()`, every incoming request undergoes JWT authentication.

For example, in the GET endpoint below, the global guard validates and decodes the JWT access token and attaches the user information to the request object, making it accessible in the controller methods. 

<script src="https://gist.github.com/SipiosMarket/36dcaf2928443d054dbefd9b05455a27.js"></script>

```tsx
//app.controller.ts
import { Controller, Get, Request } from '@nestjs/common';
import { AppService } from './app.service';
import { AccessTokenPayload } from './auth/types/AccessTokenPayload';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  async getHello(@Request() req): Promise<string> {
    const accessTokenPayload: AccessTokenPayload =
      req.user as AccessTokenPayload;
    return await this.appService.getHello(accessTokenPayload.userId);
  }
```

### Specify public routes

You have now the option to apply the `@Public()`decorator to methods or classes that don't require JWT authentication. In our scenario, we apply this decorator to the `AuthController`.

<script src="https://gist.github.com/SipiosMarket/d41512d0af2296ec1eaeea27abab1157.js"></script>

```
//auth.controller.ts
@Public()
@Controller('auth')
export class AuthController {
  //...
}
```

### Create **custom decorators**

In order to make your code more readable and transparent, you can create your own **custom decorator** and reuse it across all of your controllers. It’s a very good practice to avoid code duplication.

Depending on the specific requirements of your project, (e.g.  [Role-based access control](https://medium.com/@dev.muhammet.ozen/role-based-access-control-in-nestjs-15c15090e47d)), you can create numerous and various decorators and guards to address diverse authorization scenarios. For this article we will only create a  `@User()` decorator that extracts the user information from the request object in the NestJS execution context.

<script src="https://gist.github.com/SipiosMarket/6f8a2e8c6413fab02157b0a54eb39146.js"></script>

```tsx
// user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

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

Then you just have to pass it to your controller to retrieve the user information encapsulated in your request.

<script src="https://gist.github.com/SipiosMarket/38044e99bb4035c1129663e85a7f3d93.js"></script>

```tsx
//app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { User } from './auth/decorators/user.decorator';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  async getHello(@User() user): Promise<string> {
    return await this.appService.getHello(user.id);
  }
}
```

## Conclusion


![Dependancies schema of the authentication system](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ut7bwhtuxk8boaeyirrj.png)

In conclusion, **crafting a secure and efficient authentication system in a NestJS application is a meticulous but easy process**.  We have integrated Passport to handle various authentication strategies, used JWT tokens to enhance security, and the global guards to ensure consistent authentication across the entire project.  By prioritizing security, simplicity, and extensibility in our implementation, we have established a foundation that not only safeguards user data but also allows for seamless integration of future features.

By following this step-by-step guide, you can establish a robust authentication system in your NestJS applications, promoting secure user interactions and data protection. The provided code snippets serve as a foundation for building upon and adapting to specific project requirements (Role-based access control, OAuth).

However, I did not get into [refresh token](https://medium.com/@chamodisamarawickrama/understanding-refresh-tokens-f27629a096f8) even though they are an essential component of secure authentication systems, particularly in scenarios where long-lived sessions are required. A deep dive into implementing refresh tokens in a NestJS context could be valuable for you if you aim to build robust and secure authentication systems.

### Sources

- [Documentation NestJs](https://nestjs.com/)
- [Adding Salt to Hashing: A Better Way to Store Passwords](https://auth0.com/blog/adding-salt-to-hashing-a-better-way-to-store-passwords/)