Authentication & Authorization Using Nest Js (TypeScript) with MongoDb

Aniket Tikone
10 min readOct 21, 2021

--

Source:Google.com

Before We start with the coding part we will go through some core concepts about the same,you can also skip this part if already known !

Authentication & Authorization :

Authentication confirms that user’s are valid and has key to access specific things or simply verification of user’s identity ,for example: facebook login

Whereas Authorization is something which gives permission to access some forbidden resources. for example: An access to facebook features after successful login

JWT’s:

Pronounced as JOT short for JSON Web Tokens which are designed to provide a secure way of transmitting information between client and server’s or in our case sharing user’s encrypted information between the API Server and client server.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFuaWtldEBnbWFpbC5jb20iLCJyb2xlIjoiY2xp.ZW50IiwiaWF0IjoxNjMzNzc0NTUzLCJleHAiOjE2MzM3Nzc1NTN9.tfzfmMzBqwtx_S-01P7-rdk6o80c3jBhwNNl-gluAWc

A base64 JWT token could look like this which has some encrypted data called as Payload which is split by three dot’s (.)

Signature:

Signature’s are specific phrases or words which are attached to JWT to generate encrypted tokens.

Now let’s start with the actual implementation of the code

Note:We will be creating a normal user’s login flow where an user will be validated over his/her appropriate username/email and password !

Step 1:

initialize the nest project using below command

nest new nest-user-auth

A question will be asked on Which package manager would you ❤️ to use?
❯ npm Or yarn

choose any of the above

then create auth module,auth controller ,auth service ,user module,user controller and user service for reusable code such as crud database operations in which

Controllers

Controllers are responsible for handling incoming requests and returning responses to the client.

Module

A module is a class annotated with a @Module() decorator. The @Module() decorator provides metadata that Nest makes use of to organize the application structure. Thus, for most applications, the resulting architecture will employ multiple modules, each encapsulating a closely related set of capabilities.

Services/Providers

Providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider — services, repositories, factories, helpers, and so on. The main idea of a provider is that it can be injected as dependency; this means objects can create various relationships with each other, and the function of “wiring up” instances of objects can largely be delegated to the Nest runtime system.

I’m following below folder structure for same

source:Gallery

note:each functionality will act as an independent module which can be injected in other classes or in other modules. ( learn about DI here )

Step 2: Create an User account

Before we can authenticate a User, it has to exist in the database. So the first thing to do would be to create an endpoint to allow a User to create an account.

Hence lets start with the connecting & creating mongoose database in our project

Firstly install mongoose and ‘@nestjs/mongoose ’ libraries then

Go to app.module.ts and initialise the mongoose connection on app start

import { Module, Global } from ‘@nestjs/common’;import { MongooseModule } from ‘@nestjs/mongoose’;@Module({imports: [MongooseModule.forRoot(‘mongodb:localhost:27017/yourCollectionName’)],controllers: [],providers: [],})export class AppModule { }

Create a user.model.ts file in user/model folder and then add user schema properties where email should be unique

The MongooseModule provides the forFeature() method to configure the module, including defining which models should be registered in the current scope. Hence we will add this configuration in user module into the imports sections

user.module.ts

import { Module, forwardRef } from ‘@nestjs/common’;import { MongooseModule } from ‘@nestjs/mongoose’;import { User, UserSchema } from ‘./model/user.model’;@Module({imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])
],
controllers: [],providers: [],exports: [],})export class UserModule {}

Once you’ve registered the schema, you can inject a User model into the UserService using the @InjectModel() decorator

Let’s create a user.service.ts file and add the below code in it to inject User model so that we could perform crud operations on it

@Injectable() decorator is used to inject a user model in user service

// user.service.ts@Injectable()export class UserService {constructor(@InjectModel(User.name) private userModel: Model<UserDocument>)

and then add some reusable crud functions such as findOne() ,find(), findOneAndUpdate() ,etc using user model instance .

Here’s the full version of user.service.ts file

Now let’s create user.controller.ts for creating user account route

To make it functional we have to inform user module class about the user controller’s and providers

Refer below code for user module

As we’re also using auth service instance in user service class hence we need to add it’s reference to user module file that we’re using it and also we’ll be using userService instance in other module’s as well hence we need to add User Service in exports sections of user module

and then add user module reference in the import section of the app.module.ts file as its a root module of nest app

// app.module.tsimport { Module } from ‘@nestjs/common’;import { MongooseModule } from ‘@nestjs/mongoose’;import { UserModule } from ‘./api/user/user.module’;@Module({imports: [MongooseModule.forRoot(‘localhost:mongoose/url’),UserModule,],controllers: [],providers: [],})export class AppModule { }

Now start the app using command npm run start and create an user from postman.

user is created now !

Step 3:Authenticating User:

Generating Access ( JWT ) Token

To log in, a user will have to send a POST request to /auth/login with their email and password. In exchange, they will receive a JWT token which contains some basic user information and an expiry timestamp of that token .

So, Let’s create a route handler for login in the auth.controller.ts:

//auth.controller.ts
async login(@Request() req): Promise<any> {
try {//...//} catch (error) {throw error;}}

Validating User

To validate an user, we will create a validateUser method in the auth.service.ts This should accept an email and plaintext password, internally it will find an user with it’s email with the help of user service and then use the comparePassword method which is using bcrypt library to check the password. If the user has been found and the password is also correct, then it should return a User object, otherwise it should return an exception.

//auth.service.tsasync validateUser(email: string, pass: string): Promise<any> {const query = { email: email };const user = await this.UserService.findOne(query);if (!user) throw new NotFoundException(‘Email Does not exist’);const isMatched = await this.comparePasswords(pass, user.password);if (!isMatched) throw new UnauthorizedException(‘Invalid Password’);return user;}
//compare password
async comparePasswords(password: string,hashedPassword: string): Promise<any> {return bcrypt.compare(password, hashedPassword).then((isMatch) => {if (isMatch) return true;return false;}).catch((err) => err);}

Remember that we’ve used auth service method to generate hashed password for creating user account in user service ?,here’s the code snippet for same in which I’m using bcrypt library with 10 as hard coded salt rounds which will return a hashed password .

// auth.service.tsasync getHashedPassword(password: string): Promise<any> {return new Promise((resolve, reject) => {bcrypt.hash(password, 10, (err, hash) => {if (err) {reject(err);}resolve(hash);});});}

Building a Local Strategy

Lets Install Passport and Passport Local Dependencies:

npm i — save @nestjs/passport passport passport-local
npm i — save-dev @types/passport-local

To implement a local strategy, we should extend the PassportStrategy from the passport package and register it as a provider in the auth.module.ts.

// auth.module.tsimport { Module, forwardRef } from ‘@nestjs/common’;import { PassportModule } from ‘@nestjs/passport’;import { AuthController } from ‘./auth.controller’;import { AuthService } from ‘./auth.service’;import { LocalStrategy } from ‘./localStrategy’;import { UserModule } from ‘../user/user.module’;@Module({imports: [forwardRef(() => UserModule),PassportModule,],controllers: [AuthController],providers: [AuthService, LocalStrategy],exports: [AuthService],})export class AuthModule { }

For the local-strategy, Passport expects a validate method which we can override with our own logic with the following parameters : validate(username: string, password:string): any.The strategy will be @Injectable so we can use it in any modules that import the AuthModule.

Inside the auth folder create a new file called localStrategy.ts:

Login Guard route

With the local strategy we can now implement an authentication to the /auth/login route, and will apply the built-in guard to initiate the passport-local flow.

Open the auth.controller.ts file and add an user guard from @nestjs/common :

import { Controller,Post,Logger,Request,UseGuards,} from @nestjs/common’;import { AuthGuard } from ‘@nestjs/passport’;@Controller(‘auth’)export class AuthController {logger: Logger;constructor() {this.logger = new Logger(AuthController.name);}@UseGuards(AuthGuard(‘local’))@Post(‘login’)async login(@Request() req): Promise<any> {try {return req.user;} catch (error) {throw error;}}}

With @UseGuards(AuthGuard('local')) we are using an AuthGuard that @nestjs/passportautomatically provides for us when we extended the passport-local strategy. Our Passport local strategy has a default name of 'local'. We reference that name in the @UseGuards() decorator to associate it with code supplied by the passport-local package. This is used to guide which strategy to invoke in case we have multiple Passport strategies .

lets test the login route with local strategy on postman

curl — location — request POST ‘localhost:3020/auth/login’ \ — header ‘Content-Type: application/json’ \ — data-raw ‘{“email”:”Aniket09@gmail.com”,“password”:”password”}’

Local Strategy is working !

Passing strategy directly to the AuthGaurd(). Works flawlessly but it adds more confusion when number of strategies are more so I mostly recommend creating my own classes to manage guards .

with overridden handleRequest. method to handle strategy errors.

now import this guard into the controller and directly use it into @UseGuards decorator like below code:

// auth.controller.tsimport { Controller,Post,Logger,Request,UseGuards } from ‘@nestjs/common’;import { LocalAuthGuard } from ‘./local-auth.gaurd’;@Controller(‘auth’)export class AuthController {logger: Logger;constructor() {this.logger = new Logger(AuthController.name);}@Post(‘login’)@UseGuards(LocalAuthGuard)async login(@Request() req): Promise<any> {try {return req.user;} catch (error) {throw error;}}}

Step 4 : Authorization of an user using jwt

Now we’re ready to move onto the jwt part in which

  1. User will login through local strategy and on success it’s route will return an encrypted jwt token
  2. That JWT valid token can be used as bearer token on protected API routes.

To use passport-jwt with Nest, we also need to install @nestjs/jwt.

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

Next, we’ll need to register the JWT Service from @nestjs/jwt in auth.module.ts. The JwtModule requires a secret and expiry time which you can store in .env file

// auth.module.ts import { Module, forwardRef } from ‘@nestjs/common’;import { PassportModule } from ‘@nestjs/passport’;import { JwtModule } from ‘@nestjs/jwt’;import { AuthController } from ‘./auth.controller’;import { AuthService } from ‘./auth.service’;import { LocalStrategy } from ‘./localStrategy’;import { UserModule } from ‘../user/user.module’;@Module({imports: [JwtModule.register({secret: ‘Your_JWT_SECRET’,signOptions: { expiresIn: ‘3000s’ },}),forwardRef(() => UserModule),PassportModule,],controllers: [AuthController],providers: [AuthService, LocalStrategy],exports: [AuthService],})export class AuthModule { }

JWT Strategy

Just like the Local Strategy, we also need a JWT Strategy. passport-jwt provides an ExtractJwt.fromAuthHeaderAsBearerToken function which we can pass through to the super call in the PassportStrategy. The constructor needs secret key of JWT which should be same as what we’re provided to the auth module

Passport JWT will guarantee that the token received by the validate method of its strategy is a valid token, which has been correctly signed and has not expired yet. Then it is up to us to return the information that will be assigned to user on the Request object.

First let’s register JWT Strategy with auth.module.ts

so here’s the full version of auth.module.ts

Also let’s create another JWT Auth Gaurd

To prove that the jwt gaurd is working we’ll be adding it to the another route using @UseGaurd decorator

// auth.controller.ts@UseGuards(JwtAuthGuard)@Get(‘viewProfile’)async getUser(@Request() req): Promise<any> {return req.user;}

we can also define that which data the jwt token should store ,for that we’ll be creating a generic method in auth service to generate jwt token

// auth.service.tsasync generateJwtToken(user: any) {const payload = {email: user.email};return {access_token: this.jwtService.sign(payload),};}

and to store user’s data after successful login we will be adding this method into the auth.controller’s login route

// auth.controller.ts@Post(‘login’)@UseGuards(LocalAuthGuard)async login(@Request() req): Promise<any> {try {//return req.user;return await this.authService.generateJwtToken(req.user);} catch (error) {throw error;}}

here’s the full version of auth.controller.ts with protected routes

Now start the app again using npm run start and test the JWT protected routes after login

On Successful Login

Login route will return an access token i.e JWT token on success which we have to provide to the protected route in the header bearer field for it’s access .

It will return an user’s info on a valid access token

and what if the token is invalid ?

refer below screenshot for same !

Recap

This was a very lengthy article but we’ve covered a lot of basics .

We’ve:

  • Created a User module which provides a User service for interacting with the database. The service will allow you to find a User by its email address and also to create/modify/delete an user .
  • Created an Auth Controller with routes for login and protected view profile after successful login.
  • Created an Auth service which will authenticate the User using their email address and password i.e Login Route , and returns a JWT token to allow them to access other user protected API endpoints.
  • Created a Local Guard for login and JWT Guard that will read the JWT token from header bearer which will either permit or deny access to the protected endpoints.

Here’s the Git-hub repository of the above article https://github.com/annytikone/nest-user-auth

Happy Coding….

--

--

Aniket Tikone

A Software Engineer who's in love with JS and Blockchain