npm i @nestjs/jwt @nestjs/passport passport passport-jwt @types/passport-jwt @types/passport
import { MinLength , IsString , MaxLength , Matches } from 'class-validator' ;
export class AuthCredentailsDto {
@IsString ( )
@MinLength ( 4 )
@MaxLength ( 20 )
username : string ;
@IsString ( )
@MinLength ( 8 )
@MaxLength ( 25 )
@Matches ( / ( (? = .* \d ) | (? = .* \W + ) ) (? ! [ . \n ] ) (? = .* [ A - Z ] ) (? = .* [ a - z ] ) .* $ / , {
message : 'Password is too weak' ,
} )
password : string ;
}
import {
BaseEntity ,
Entity ,
PrimaryGeneratedColumn ,
Column ,
Unique ,
} from 'typeorm' ;
// Bcrypt for password encryption
import * as bcrypt from 'bcrypt' ;
@Entity ( )
// Unique keys
@Unique ( [ 'username' ] )
export class User extends BaseEntity {
@PrimaryGeneratedColumn ( )
id : number ;
@Column ( )
username : string ;
@Column ( )
password : string ;
@Column ( )
salt : string ;
// Entity function
async validatePassword ( password : string ) : Promise < boolean > {
const hash = await bcrypt . hash ( password , this . salt ) ;
return hash === this . password ;
}
}
import { EntityRepository , Repository } from 'typeorm' ;
import * as bcrypt from 'bcrypt' ;
import { User } from './user.entity' ;
import { AuthCredentailsDto } from './dto/auth-credentials.dto' ;
import {
ConflictException ,
InternalServerErrorException ,
} from '@nestjs/common' ;
@EntityRepository ( User )
export class UserRepository extends Repository < User > {
async signUp ( authCredentailsDto : AuthCredentailsDto ) : Promise < void > {
const { username, password } = authCredentailsDto ;
const user = new User ( ) ;
user . username = username ;
user . salt = await bcrypt . genSalt ( ) ;
user . password = await this . hashPassword ( password , user . salt ) ;
try {
await user . save ( ) ;
} catch ( error ) {
if ( error . code === '23505' ) {
// Duplicate username
throw new ConflictException ( 'Username already exists' ) ;
} else {
throw new InternalServerErrorException ( ) ;
}
}
}
async validateUserPassword (
authCredentailsDto : AuthCredentailsDto ,
) : Promise < string > {
const { username, password } = authCredentailsDto ;
const user = await this . findOne ( { username } ) ;
if ( user && ( await user . validatePassword ( password ) ) ) {
return user . username ;
}
return null ;
}
private async hashPassword ( password : string , salt : string ) : Promise < string > {
return await bcrypt . hash ( password , salt ) ;
}
}
import { Module } from '@nestjs/common' ;
import { JwtModule } from '@nestjs/jwt' ;
import { PassportModule } from '@nestjs/passport' ;
import { AuthController } from './auth.controller' ;
import { AuthService } from './auth.service' ;
import { TypeOrmModule } from '@nestjs/typeorm' ;
import { UserRepository } from './user.repository' ;
import { JwtStrategy } from './jwt.strategy' ;
@Module ( {
imports : [
JwtModule . register ( {
secret : 'topSecret51' ,
signOptions : {
expiresIn : 3600 ,
} ,
} ) ,
PassportModule . register ( {
defaultStrategy : 'jwt' ,
} ) ,
TypeOrmModule . forFeature ( [ UserRepository ] ) ,
] ,
controllers : [ AuthController ] ,
providers : [ AuthService , JwtStrategy ] ,
exports : [ JwtStrategy , PassportModule ] ,
} )
export class AuthModule { }
import { PassportStrategy } from '@nestjs/passport' ;
import { Strategy , ExtractJwt } from 'passport-jwt' ;
import { Injectable , UnauthorizedException } from '@nestjs/common' ;
import { JwtPayload } from './jwt-payload.interface' ;
import { InjectRepository } from '@nestjs/typeorm' ;
import { UserRepository } from './user.repository' ;
import { User } from './user.entity' ;
@Injectable ( )
export class JwtStrategy extends PassportStrategy ( Strategy ) {
constructor (
@InjectRepository ( UserRepository )
private userRepository : UserRepository ,
) {
super ( {
jwtFromRequest : ExtractJwt . fromAuthHeaderAsBearerToken ( ) ,
secretOrKey : 'topSecret51' ,
} ) ;
}
async validate ( payload : JwtPayload ) : Promise < User > {
const { username } = payload ;
const user = await this . userRepository . findOne ( { username } ) ;
if ( ! username ) {
throw new UnauthorizedException ( ) ;
}
return user ;
}
}
Creating token in the service
import { Injectable , UnauthorizedException } from '@nestjs/common' ;
import { UserRepository } from './user.repository' ;
import { InjectRepository } from '@nestjs/typeorm' ;
import { AuthCredentailsDto } from './dto/auth-credentials.dto' ;
import { JwtService } from '@nestjs/jwt' ;
import { JwtPayload } from './jwt-payload.interface' ;
@Injectable ( )
export class AuthService {
constructor (
@InjectRepository ( UserRepository )
private userRepository : UserRepository ,
private jwtService : JwtService ,
) { }
async signUp ( authCredentailsDto : AuthCredentailsDto ) : Promise < void > {
await this . userRepository . signUp ( authCredentailsDto ) ;
}
async signIn (
authCredentailsDto : AuthCredentailsDto ,
) : Promise < { accessToken : string } > {
const username = await this . userRepository . validateUserPassword (
authCredentailsDto ,
) ;
if ( ! username ) {
throw new UnauthorizedException ( 'Invalid credentials' ) ;
}
const payload : JwtPayload = { username } ;
const accessToken = await this . jwtService . sign ( payload ) ;
return { accessToken } ;
}
}
Guarding the route and extracting payload from request
import { UseGuards } from '@nestjs/common' ;
import { AuthGuard } from '@nestjs/passport' ;
@Controller ( 'auth' )
export class AuthController {
@Post ( 'test' )
// Guarding the route
@UseGuards ( AuthGuard ( ) )
test ( @Req ( ) req ) {
console . log ( req . user ) ;
}
}
Creating a custom decorator to extract user from request
import { createParamDecorator } from '@nestjs/common' ;
import { User } from './user.entity' ;
export const GetUser = createParamDecorator (
( data , req ) : User => {
return req . user ;
} ,
) ;
Using the custom decorator
import { UseGuards } from '@nestjs/common' ;
import { AuthGuard } from '@nestjs/passport' ;
@Controller ( 'auth' )
export class AuthController {
@Post ( 'test' )
// Guarding the route
@UseGuards ( AuthGuard ( ) )
test ( @GetUser ( ) user : User ) {
console . log ( user ) ;
}
}