Nest with JWT 🔒
Author : JaNakh Pon , August 16, 2021
Tags
How to add JWT 🔒 to Nest.js
We are going to add JWT to the previous api built with Nest.js, TypeORM and PostgresDB in a few steps:
- installation and setup
- helper functions/services
- implement JWT for protected routes
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. Summary
In this article, we are going to add JWT authentication to our NEST.JS API . To do that, we need a new table to store user information in PostgresDB, and we will expose two routes for users to register and login.
When a user registers an account to access private route, we will use that data and hash the user password and save it to Databse.
When the user login using the same credentials, we will hash the password and match it to the hashed password stored in Database and if it matches we will give the user a token to access private route.
Implementation
Firstly, let's install the required dependencies:
> npm i @nestjs/passport @nestjs/jwt passport passport-jwt bcrypt --save
> npm i -D @types/passport-jwt @types/bcrypt
> nest g m user && nest g c user && nest g s user
> cd user && mkdir auth && nest g s jwt-strategy && nest g s password
Now, we have all the files we want to work with, so let's start working on jwt-strategy:
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { UserService } from '../../user.service'
@Injectable()
export class JwtStrategyService extends PassportStrategy(Strategy) {
constructor(private userService: UserService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: "helloxyz!",
});
}
async validate(payload: any) {
const isValidated = await this.userService.validateUserByEmail(payload.email);
if (isValidated) {
return { userId: payload.id, email: payload.email };
} else {
throw new UnauthorizedException('UnAuthorized');
}
}
}
And password service:
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt'
@Injectable()
export class PasswordService {
async hashPassword(password: string) {
return await bcrypt.hash(password, 10)
}
async comparePassword(plainText, encryptedPass): Promise<boolean> {
return await bcrypt.compare(plainText, encryptedPass)
}
}
Since, our helper functions/services are ready let's continue by adding methods to user.service.ts the way we did for todo in previous article:
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { JwtService } from '@nestjs/jwt';
import { PasswordService } from './auth/password/password.service'
import { UserEntity } from './entities/user.entity'
import { User, RespToken } from './interfaces/user.interface'
import { UserSignupDTO, UserSigninDTO } from './dto/user.dto'
@Injectable()
export class UserService {
constructor(
@InjectRepository(UserEntity)
private readonly UserRepository: Repository<UserEntity>,
private PasswordService: PasswordService,
private jwtService: JwtService,
) {}
async signup(user: UserSignupDTO): Promise<User> {
const uexist = await this.UserRepository.findOne({ email: user.email })
if (uexist) {
throw new UnauthorizedException(
`${user.email} is unavailable!`,
);
}
const ePass = await this.PasswordService.hashPassword(user.password)
const resp = await this.UserRepository.save({
...user,
password: ePass
})
return resp
}
async signin(user: UserSigninDTO): Promise<RespToken> {
const uexist = await this.UserRepository.findOne({ email: user.email })
if (!uexist) {
throw new UnauthorizedException(`No user found with ${user.email}!`)
}
const valid = await this.PasswordService.comparePassword(user.password, uexist.password)
if (valid) {
const token = await this.jwtService.signAsync({ email: uexist.email, id: uexist.id }, { expiresIn: '1d' },)
return { statusCode: 200, token }
} else {
throw new UnauthorizedException('Invalid Password!')
}
}
async validateUserByEmail(email: string): Promise<boolean> {
const user = await this.UserRepository.findOne({ email: email })
if (user) {
return true;
} else {
return false;
}
}
}
Now, we need to use the methods from service to handle routes in controller:
import { Body, Controller, Post, Get, UseGuards, Request } from '@nestjs/common';
import { User, RespToken } from './interfaces/user.interface'
import { UserService } from './user.service'
import { UserSignupDTO, UserSigninDTO } from './dto/user.dto'
import { AuthGuard } from '@nestjs/passport'
@Controller('user')
export class UserController {
constructor(private userService: UserService) { }
@Post('register')
async register(@Body() user: UserSignupDTO): Promise<User> {
return await this.userService.signup(user);
}
@Post('login')
async login(@Body() user: UserSigninDTO): Promise<RespToken> {
return await this.userService.signin(user);
}
@Get('me')
@UseGuards(AuthGuard('jwt'))
async profile(@Request() req) {
return req.user;
}
}
Now, our auth should be working fine so, let's check it by accessing a private route
that we have defined in our controller:
For example, make a GET request to /api/v1/todos/private
using Postman, ThunderClient, curl
or whatever u want to use and you should get an Unauthorized
error meassage.
So, to be able to access that route, you should create an account via /user/register
.
After that, login via /user/login
with the same credentials that you have used for creating an account and grab the token.
And add an Authorization Header to Postman for example, 'Authorization':Bear {token}
and access the /api/v1/todos/private
again but this time you should be able to get the data 😉.
Go Back.