NestJS Unknown authentication strategy "local" - node.js

I have this code in NestJs with the following files below, but now it says "Unknown authentication strategy "local"",
I've already looked for solutions and they all point to an import error, but I have the localstrategy imported into auth.module and app.module (I've already tested taking it out of app.module but it doesn't change anything.)
app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ThrottlerModule } from '#nestjs/throttler';
import { MongooseModule } from '#nestjs/mongoose';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { LocalStrategy } from './auth/local.strategy';
#Module({
imports: [
MongooseModule.forRoot(
'mongodb+srv://user:password#db.db.mongodb.net/db?retryWrites=true&w=majority',
),
ThrottlerModule.forRoot({
ttl: 60,
limit: 10,
}),
AuthModule,
UsersModule,
],
controllers: [AppController],
providers: [AppService, LocalStrategy],
})
export class AppModule {}
auth.module.ts
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '#nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtModule } from '#nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
import 'dotenv/config';
#Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: process.env.JWTSECRET,
signOptions: { expiresIn: '60s' },
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthService } from './auth.service';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
app.controller.ts
import {
Controller,
Request,
Post,
UseGuards,
Res,
Get,
Body,
} from '#nestjs/common';
import { AuthService } from './auth/auth.service';
import { MakeAuthDto } from './auth/dto/make-auth.dto';
import { JwtAuthGuard } from './auth/jwt-auth.guard';
import { LocalAuthGuard } from './auth/local-auth.guard';
import { Roles } from './utils/decorators/roles.decorator';
import { Role } from './utils/enums/role.enum';
import { RolesGuard } from './utils/guards/roles.guard';
#Controller()
export class AppController {
constructor(private authService: AuthService) {}
#UseGuards(LocalAuthGuard)
#Post('auth/login')
async login(
#Body() _: MakeAuthDto,
#Request() req,
#Res({ passthrough: true }) res,
) {
console.log(req.user);
const access_token = await this.authService.login(req.user);
res.cookie('jwt', access_token);
return req.user;
}
#UseGuards(JwtAuthGuard, RolesGuard)
#Roles(Role.Admin)
#Get('tests')
getProfile(#Request() req) {
return req.user;
}
}
local-auth.guard.ts
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
auth.service.ts
import { Injectable } from '#nestjs/common';
import { UsersService } from 'src/users/users.service';
import { JwtService } from '#nestjs/jwt';
import { UserDocument } from 'src/users/entities/user.entity';
#Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(email: string, pass: string): Promise<UserDocument | any> {
const user = await this.usersService.findOne(email);
if (user && (await user.compareHash(pass))) {
const { password, ...result } = user.toObject();
await this.usersService.updateLastLogin(user._id);
return result;
}
return null;
}
async login(user: UserDocument): Promise<string> {
const payload = { email: user.email, sub: user._id, roles: user.roles };
return this.jwtService.sign(payload);
}
}
jwt-auth.guard.ts
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import 'dotenv/config';
const cookieExtractor = function (req) {
let token = null;
if (req && req.cookies) {
token = req.cookies['jwt'];
}
return token;
};
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromExtractors([cookieExtractor]),
ignoreExpiration: false,
secretOrKey: process.env.JWTSECRET,
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email, roles: payload.roles };
}
}
No similar question on stackoverflow solved my problem, does anyone know what it can be?

Related

Nestjs returns 401 (Unauthorized) even with valid user ft. passport-local

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.k-s#lloydsbanking.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;
}
}

Nest.js Passport.js Google conflicts with Apple

I have created auth for application by google (passport-google-oauth20) and apple (passport-appleid). When i try to invoke GET google-sign-in, I have the following error
[Nest] 987 - 07/22/2022, 9:49:18 AM ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'callbackURL')
TypeError: Cannot read properties of undefined (reading 'callbackURL')
at GoogleStrategy.OAuth2Strategy.authenticate (/home/node/app/node_modules/passport-appleid/lib/strategy.js:76:59)
at attempt (/home/node/app/node_modules/passport/lib/middleware/authenticate.js:369:16)
at authenticate (/home/node/app/node_modules/passport/lib/middleware/authenticate.js:370:7)
at /home/node/app/node_modules/#nestjs/passport/dist/auth.guard.js:96:3
at new Promise (<anonymous>)
at /home/node/app/node_modules/#nestjs/passport/dist/auth.guard.js:88:83
at MixinAuthGuard.<anonymous> (/home/node/app/node_modules/#nestjs/passport/dist/auth.guard.js:49:36)
at Generator.next (<anonymous>)
at fulfilled (/home/node/app/node_modules/#nestjs/passport/dist/auth.guard.js:17:58)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
If i commented Apple Strategy in user module, everything work fine.
I can't understand why GoogleStrategy.OAuth2Strategy.authenticate go to the appleid packages
And If I use only Google Strategy or only Apple Strategy everything works.
#UseGuards(AuthGuard('google'))
#ApiOperation({ summary: 'google sign in' })
#Get('google-sign-in')
public async signInWithGoogle(#Req() req: any) {
return req.user;
}
#UseGuards(AuthGuard('google'))
#ApiOperation({ summary: 'google redirect' })
#Get('auth/google/redirect')
public async signInWithGoogleRedirect(#Req() req: any) {
return this.userService.socialSignIn(req.user);
}
#UseGuards(AuthGuard('apple'))
#ApiOperation({ summary: 'apple sign in' })
#Get('apple-sign-in')
public async signInWithApple(#Req() req: any) {
return req.user;
}
#UseGuards(AuthGuard('apple'))
#ApiOperation({ summary: 'apple redirect' })
#Get('apple/redirect')
public async signInWithAppleRedirect(#Req() req: any) {
return this.userService.socialSignIn(req.user);
}
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Profile } from 'passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { RegisteredPlatformsEnum } from 'src/_enum/registered-platforms.enum';
import { ConfigService } from '../config.services';
#Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
constructor(configService: ConfigService) {
super({
clientID: configService.get('GOOGLE_AUTH_CLIENT_ID'),
clientSecret: configService.get('GOOGLE_AUTH_CLIENT_SECRET'),
callbackURL: configService.get('GOOGLE_AUTH_CALLBACK_URL'),
scope: ['email', 'profile'],
});
}
public async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifyCallback,
): Promise<any> {
const { name, emails } = profile;
const [userEmail] = emails;
const user = {
email: userEmail.value,
name: name.givenName,
registeredPlatform: RegisteredPlatformsEnum.GOOGLE,
accessToken,
};
done(null, user);
}
}
import path from 'path';
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Profile } from 'passport';
import { Strategy, VerifyCallback } from 'passport-appleid';
import { RegisteredPlatformsEnum } from 'src/_enum/registered-platforms.enum';
import { ConfigService } from '../config.services';
#Injectable()
export class AppleStrategy extends PassportStrategy(Strategy, 'apple') {
constructor(configService: ConfigService) {
super({
clientID: configService.get('APPLE_AUTH_CLIENT_ID'),
callbackURL: configService.get('APPLE_AUTH_CALLBACK_URL'),
teamId: configService.get('APPLE_AUTH_TEAM_ID'),
keyIdentifier: configService.get('APPLE_AUTH_KEY_IDENTIFIER'),
privateKeyPath: path.join(
__dirname,
`./AuthKey_${configService.get('APPLE_AUTH_KEY_IDENTIFIER')}.p8`,
),
});
}
public async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifyCallback,
): Promise<any> {
const { name, emails } = profile;
const [userEmail] = emails;
const user = {
email: userEmail.value,
name: name.givenName,
registeredPlatform: RegisteredPlatformsEnum.APPLE,
accessToken,
};
done(null, user);
}
}
import { Module, forwardRef, Logger } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { AppModule } from '../../app.module';
import { ConfigService } from '../../services/config.services';
import { UserService } from './services/users.service';
import { UserController } from './user.controller';
import { UserEntity } from '../../model/user.entity';
import { EmailService } from '../../services/email.service';
import { UserRepository } from 'src/repositories/user.repository';
import { GoogleStrategy } from 'src/services/strategy/google-strategy';
import { AppleStrategy } from 'src/services/strategy/apple-strategy';
#Module({
imports: [
forwardRef(() => AppModule),
TypeOrmModule.forFeature([UserEntity, UserRepository]),
],
controllers: [UserController],
providers: [
Logger,
UserService,
ConfigService,
EmailService,
// passport strategies
AppleStrategy,
GoogleStrategy,
],
exports: [UserService],
})
export class UserModule {}

how to change return req.user object name of passport-local?

I'm developing a backend using nestjs and passport-local strategy. I use local strategy only for owners login in my projects. but at end of validation it returns the owner in req.user.
how can I change it so it returns the owner in req.owner?
import { Injectable } from '#nestjs/common';
import { OwnerService } from '../owner/owner.service';
#Injectable()
export class AuthService {
constructor(private ownerService: OwnerService) {}
async validateOwner(username: string, pass: string): Promise<any> {
const owner = await this.ownerService.findOneByUsername(username);
// later check with hashed pass
if (owner && owner.owner && owner.owner.password === pass) {
const { password, ...result } = owner.owner;
return result;
}
return null;
}
}
and
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, HttpException } from '#nestjs/common';
import { AuthService } from './auth.service';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
usernameField: 'username',
passwordField: 'password',
});
}
async validate(username: string, password: string): Promise<any> {
const owner = await this.authService.validateOwner(username, password);
if (!owner) {
throw new HttpException('No Owner found', 404);
}
return owner;
}
}
how I use it:
#UseGuards(AuthGuard('local'))
#Post('login')
async login(#Request() req) {
console.log(req.owner, req.user);
return req.owner;
}
req.owner is empty but req.user has value
UPDATE:
my Authmodule.ts:
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { OwnerModule } from '../owner/owner.module';
import { LocalStrategy } from './local.strategy';
import { PassportModule } from '#nestjs/passport';
import { JwtModule } from '#nestjs/jwt';
#Module({
providers: [AuthService, LocalStrategy],
imports: [
OwnerModule,
PassportModule.register({
property: 'owner',
})
],
exports: [AuthService],
})
export class AuthModule {}
PassportModule.register({ property: 'owner' })
where PassportModule is imported from #nestjs/passport
I created a LocalAuthGuard class that extends my local strategy:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
constructor() {
super({
property: 'owner',
});
}
}
then wherever I use this guard, it adds my target field to req.owner instead of req.user.
#UseGuards(LocalAuthGuard)
#Post('login')
async login(#Request() req): Promise<LoginResponse> {
return this.authService.login(req.owner);
}

NestJS passport-jwt always throws Unauthorized

I encountered to the issue that whenever I sign up through the postman I receive the token. However, other route is private and requires token as Authorization Bearer, but whenever I put the token I receive "Unauthorized". The validate from strategy never executes, as I understand because the token for some reasons is invalid. Important to mention, that I do not receive any errors.
jwt.strategy.ts:
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(config: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get('JWT_SECRET'),
});
}
async validate(payload: any) {
console.log({ payload: payload });
return true;
}
}
auth.module.ts
import { Module } from '#nestjs/common';
import { JwtModule } from '#nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategy';
#Module({
imports: [JwtModule.register({})],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
auth.service.ts where I receive token:
public async signin(body: AuthDto) {
// find the user by email
const user = await this.prisma['User'].findUnique({
where: {
email: body.email,
},
});
// if user does not exists throw exception
if (!user) {
throw new ForbiddenException('Invalid email address or password');
}
// compare passwords
const passwordMatches = await argon.verify(user.hash, body.password);
// if password is inoccrect throw exception
if (!passwordMatches) {
throw new ForbiddenException('Invalid email address or password');
}
return this.signToken(user.id, user.email);
}
auth.service.ts where I create the token:
private async signToken(userId: number, email: string): Promise<{ access_token: string }> {
const payLoad = {
sub: userId,
email,
};
const token = await this.jwt.sign(payLoad, {
expiresIn: '10m',
secret: this.config.get('JWT_SECRET'),
});
return { access_token: token };
}
}
user.controller.ts where the private route is
import { Controller, Get, UseGuards } from '#nestjs/common';
import { JwtGuard } from 'src/auth/guard';
#Controller('users')
export class UserController {
#UseGuards(JwtGuard)
#Get('me')
getMe() {
return 'Hello JWT';
}
}
jwt.guard.ts
import { AuthGuard } from '#nestjs/passport';
export class JwtGuard extends AuthGuard('jwt') {
constructor() {
super();
}
}
The code works fine, without any issues. However, the issues was with the postman. For some reasons the Authorization tab were bugged, tried to sent through headers written by hand and that worked without any problems

LocalAuthGuard not working in nestjs app with typeorm and passport-local

I am using nestjs 8.0 with typeorm, passport-jwt, and passport-local. Everything seems to be working fine other than the LocalAuthGuard. I am able to successfully create a new user and even use the routes that have JwtAuthGuard but LocalAuthGuard seems to have some issues as I keep getting 401 Unauthorized error
Also, is there a way to console log some output from within the LocalAuthGuard or LocalStrategy?
auth.controller.ts
#Controller(['admin', 'user'])
export class AuthController {
constructor(
private authService: AuthService,
) {}
#UseGuards(LocalAuthGuard)
#Post('login')
login(#Request() req) {
console.log('object');
if (req.path.includes('admin') && !req.user.isAdmin) {
throw new UnauthorizedException();
}
return this.authService.login(req.user);
}
...
}
local.guard.ts
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
local.strategy.ts
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(usernameOrEmail: string, password: string): Promise<any> {
const user = await this.authService.validateUser({
usernameOrEmail,
password,
});
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
auth.service.ts
#Injectable()
export class AuthService {
constructor(
#InjectRepository(User)
private userRepository: Repository<User>,
private jwtService: JwtService,
) {}
async validateUser({ usernameOrEmail, password }: LoginDto) {
const user = (await this.userRepository.findOne({ username: usernameOrEmail })) ||
(await this.userRepository.findOne({ email: usernameOrEmail }));
if (user && (await bcrypt.compare(password, user.password))) {
return user;
}
return null;
}
...
}
auth.module.ts
#Module({
imports: [
TypeOrmModule.forFeature([User]),
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
return {
secret: configService.get('JWT_KEY'),
signOptions: {
expiresIn: '6000s',
},
};
},
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}
Any help or suggestion is greatly appreciated.
EDIT
It seems for LocalAuthGuard, username, and password are a must and other properties are optional.
You can create multiple local strategies with different parameters. For example,
Username and password
Phone and OTP
Email and password
Email and OTP
For using multiple local strategies, refer to this answer
Then, you can also pass an options object to specify different property names, for example: super({ usernameField: 'email', passwordField: 'otp' })
implement local strategy as follows
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Inject, Injectable, UnauthorizedException } from '#nestjs/common';
import { IAuthService } from '../services';
import { LoginDto, OtpLoginDto } from '../dto';
import { UserDto } from 'src/modules/user/dto';
import { plainToClass } from 'class-transformer';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(
#Inject(IAuthService)
private readonly authService: IAuthService,
) {
super({
usernameField: 'user_name',
passwordField: 'otp',
});
}
async validate(user_name: string, otp: string): Promise<any> {
const loginDto = plainToClass(OtpLoginDto, {
username: user_name,
otp: otp,
});
const user: UserDto = await this.authService.verifyOtp(loginDto);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
ref: customize passport

Resources