Hi awesome developers,
I'm trying to implement Authentication using passport-local and Nestjs with reference to https://progressivecoder.com/how-to-implement-nestjs-passport-authentication-using-local-strategy/.
I have implemented exactly same but Nestjs always returns 401 Unauthorized even with valid user. I can't seem to find what I am missing.
Code Structure
Authentication Module
User Module
Here's the code:
Authentication Module:
authentication.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { UserModule } from 'src/user/user.module';
import { AuthenticationController } from './controllers/authentication.controller';
import { AuthenticationService } from './services/authentication.service';
import { LocalStrategy } from './strategies/local.strategy';
@Module({
imports:[UserModule, PassportModule],
controllers: [AuthenticationController],
providers: [AuthenticationService, LocalStrategy]
})
export class AuthenticationModule {}
authentication.controller.ts
import { Controller, Post, Request, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { UserService } from 'src/user/services/user.service';
@Controller('authentication')
export class AuthenticationController {
constructor(private userService: UserService){}
@UseGuards(AuthGuard('local'))
@Post('signin')
async signin(@Request() req){
return req.user;
}
}
authentication.service.ts
import { Injectable } from '@nestjs/common';
import { UserService } from 'src/user/services/user.service';
@Injectable()
export class AuthenticationService {
constructor(private userService: UserService) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.userService.readUserByEmail(email);
if (user && user.password === password) {
const { password, ...result } = user;
return result;
}
return null;
}
}
local.strategy.ts
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-local";
import { AuthenticationService } from "../services/authentication.service";
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy){
constructor(private authenticationService: AuthenticationService){
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authenticationService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
User Module:
user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './controllers/user.controller';
import { UserService } from './services/user.service';
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService]
})
export class UserModule {}
user.controller.ts
Skipping user controller as it is irrelevent
user.service.ts
import { Injectable } from '@nestjs/common';
import { prisma } from 'src/main';
import { CreateUserDTO } from '../dto/create-user.dto';
import { UpdateUserDTO } from '../dto/update-user.dto';
@Injectable()
export class UserService {
private readonly users = [
{
id: "1",
name: "Ajitesh",
email: "ajitesh@example.com",
password: "secret"
}
]
//....other methods
async readUserByEmail(email: string){
return this.users.find(user => user.email === email);
}
}
Request:
{
"email": "ajitesh@example.com",
"password": "secret"
}
Thanks in advance.
passport-local
expects req.body
to be populated with username
and password
fields. If you plan to use something else for the username, like email
, then you need to tell passport about that in your strategy using the usernameField
option in super
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-local";
import { AuthenticationService } from "../services/authentication.service";
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy){
constructor(private authenticationService: AuthenticationService){
super({
usernameField: 'email',
});
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authenticationService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}