DEV Community

Saurabh Dashora
Saurabh Dashora

Posted on • Originally published at progressivecoder.com

How to implement NestJS Passport Authentication using Local Strategy?

In this post, we will learn how to implement NestJS Passport Authentication using the passport local strategy. Authentication is a key aspect of any production-level application. While there are many ways to handle authentication, one of the more popular ways is to use Passport.

If you are new to NestJS in general, you can start by going through this post on NestJS Basics. However, if you are mainly interested in learning how to get started with authentication in NestJS, you can continue with this post.

1 – What is Passport?

Passport is a popular NodeJS library. It is used for authentication purpose and comes along with several strategies. These strategies basically implement different authentication mechanism. For our example, we will use the local strategy. This strategy makes use of username and password.

Authentication

Passport executes a series of steps as below:

  • Authenticating a user by its credentials. These credentials can be username/password, JWT tokens or any other identity token.
  • Manage authentication state by issuing a JWT token.
  • Attach information about the user to the Request object.
  • To make things easy, the @nestjs/passport module wraps the entire passport usage pattern into familiar NestJS constructs. This makes it extremely easy to implement NestJS Passport Authentication.

2 – Install Packages

To enable the local strategy, we need to install the required packages as below:

$ npm install --save @nestjs/passport passport passport-local
$ npm install --save-dev @types/passport-local
Enter fullscreen mode Exit fullscreen mode

Passport provides the passport-local strategy that implements the username/password authentication. Also, we install the @nestjs/passport and the passport packages. Finally, the @types/passport-local package helps us with Typescript types during development.

Note - For any strategy, we need to always install the @nestjs/passport and the passport packages. The third package depends on the strategy we are trying to implement. For the local strategy, we install passport-local. Similarly, for the JWT strategy, we use the passport-jwt strategy. Even the types package depends on the strategy.

With the installation done, let’s now begin building our application.

3 – Creating the Modules

As a first step, let’s divide our application into appropriate modules. We will creating an auth-module and users-module. Basically, the auth-module will contain the logic for authenticating the user. On the other hand, the users-module will contain the user information.

With this view, we will create both the modules and their associated services as below:

$ nest g module auth
$ nest g service auth
$ nest g module users
$ nest g service users

Enter fullscreen mode Exit fullscreen mode

Next step is to build our passport strategy. Configuring a strategy has two typical steps as below:

  • First is a set of options that are specific to a particular strategy. For example, the JWT strategy needs a secret to sign the tokens.
  • Second is a verify callback. In other words, we tell passport how to verify whether a user is valid. Passport expects this callback to return the full user object if the validation is successful. Also, it should return null if validation fails. Failure can mean that the user does not exist. For a strategy like passport-local, it can also mean that the password is invalid.

The NestJS passport package helps with the above two steps by providing helper classes.

4 – Implementing the User Service

Let’s first create the user service. See below example:

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

export type User = any;

@Injectable()
export class UsersService {

    private readonly users = [
        {
            userId: 1,
            username: 'John Marston',
            password: 'rdr1',
        },
        {
            userId: 2,
            username: 'Arthur Morgan',
            password: 'rdr2',
        },
    ]

    async findOne(username: string): Promise<User | undefined> {
        return this.users.find(user => user.username === username)
    }
}
Enter fullscreen mode Exit fullscreen mode

For our demo example, our user service will simply contain a list of hardcoded valid users. However, for a real application, we would probably create a user database and fetch the users from the appropriate table using NestJS TypeORM or NestJS Sequelize libraries. You can also using MongoDB using the NestJS Mongoose library.

In the above example, the findOne() method simply fetches the user from the users array. Also, we need to update the exports array for the UsersModule as below:

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';

@Module({
  providers: [UsersService],
  exports: [UsersService]
})
export class UsersModule {}
Enter fullscreen mode Exit fullscreen mode

5 – Implementing the Auth Service

As the next step, we implement the Auth Service in the Auth Module.

See below:

import { Injectable } from '@nestjs/common';
import { UsersService } from 'src/users/users.service';

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

    async validateUser(username: string, password: string): Promise<any> {
        const user = await this.usersService.findOne(username);

        if (user && user.password === password) {
            const {password, ...result} = user
            return result
        }
        return null
    }
}
Enter fullscreen mode Exit fullscreen mode

Basically, the auth service has the job of retrieving the user and verifying the password. As you can see, we create the validateUser() method. We fetch the user by using the user service and return the user object as output. However, we strip the password property out of the object before returning it.

Finally, we update the AuthModule to import the UsersModule. Without this step, we will not be able to use the user service in the auth module.

import { Module } from '@nestjs/common';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';

@Module({
  imports: [UsersModule],
  providers: [AuthService]
})
export class AuthModule {}
Enter fullscreen mode Exit fullscreen mode

6 – Implement Passport Local Strategy

This is a key step in implementing the NestJS Passport Authentication.

Basically, we need to implement the passport local strategy. To do so, we create a file named local.strategy.ts in the auth folder.

import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-local";
import { AuthService } from "./auth.service";

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
    constructor(private authService: AuthService) {
        super()
    }

    async validate(username: string, password: string): Promise<any> {
        const user = await this.authService.validateUser(username, password);

        if (!user) {
            throw new UnauthorizedException();
        }

        return user;
    }
}
Enter fullscreen mode Exit fullscreen mode

Basically, we create a class LocalStrategy that extends the PassportStrategy class. We also pass the attribute Strategy in the class definition. Note here that the Strategy is imported from passport-local and not passport package.

In the constructor, we simply call the super() method. The local strategy only expects the username and password fields and therefore, configuration options are not needed. However, if needed, we can pass extra properties while calling super().

Next, we implement the validate() method as part of the PassportStrategy class. For every strategy, Passport will call the verify function. In NestJS, this function is implemented with the validate() method. Based on the strategy, it expects some arguments. For example, in the local strategy, it expects the username and password attributes.

From a logical point of view, this method is quite simple. It simply calls the validateUser() method from the Auth service. If a valid user is found, it returns the same. Otherwise, it throws an exception. Most of the heavy work is done in the Auth Service.

Lastly, we need to update the Auth Module as below to use the passport module.

import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';

@Module({
  imports: [UsersModule, PassportModule],
  providers: [AuthService, LocalStrategy]
})
export class AuthModule {}
Enter fullscreen mode Exit fullscreen mode

7 – Creating the Login Route

Now, we can create the login route. Basically, this route will be used to authenticate a user.

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

@Controller()
export class AppController {

  @UseGuards(AuthGuard('local'))
  @Post('login')
  async login(@Request() req) {
    return req.user;
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, we use the standard @Controller() decorator. Also, we use @Post() for the login() request handler. If you wish to know more about creating controllers, you can refer to our detailed post on NestJS Controllers.

Important thing to note here is the @UseGuards(AuthGuard(‘local’)) decorator. Basically, the AuthGuard is a special guard provided by the @nestjs/passport package. This guard was provisioned when we extended the passport-local strategy.

This built-in guard invokes the Passport strategy and starts the entire process. In other words, based on the strategy in the AuthGuard (in this case, local), this guard will retrieve the credentials, run the verify function and create the user property.

The user can simply authenticate by using the /login route as below:

$ curl -X POST http://localhost:3000/auth/login -d '{"username": "John Marston", "password": "rdr1"}' -H "Content-Type: application/json"
Enter fullscreen mode Exit fullscreen mode

In case the user is valid, we receive the user object in response. Else, we receive the HTTP Status 401 or Unauthorized.

Conclusion

With this, we have successfully learnt how to implement NestJS Passport Authentication using the passport local strategy.

We started by installing the required packages and then building the modules for retrieving users and authenticating them. Lastly, we implemented the login route to trigger the passport authentication process.

The code for this post is available on Github.

In the next post we will be implementing the NestJS Passport JWT Strategy.

If you have any comments or queries, please feel free to mention them in the comments section below.

Discussion (0)