I want to implement a global authentication guard in My NestJS application that will simply read certain headers and assign user values based on those headers, for every request that comes in.
I implemented this simple logic and registered my global guard in my main module, however for some reason all my requests fail with '401 Unauthorized'. I tried to place log messages inside internal.strategy.ts, however I don't see them getting called.
Any idea why the strategy is not called?
This is my main.ts:
import { NestFactory, Reflector } from '#nestjs/core';
import * as logging from './logging';
import { AppModule } from './app.module';
import config from './config';
import { LocalAuthGuard } from './auth/guards/local-auth.guard';
async function bootstrap(port: string | number) {
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new LocalAuthGuard())
await app.listen(port, '0.0.0.0');
logging.logger.info(`Listening on 0.0.0.0:${port}`);
}
bootstrap(config.port);
This is my auth.module.ts:
import { Module } from '#nestjs/common';
import { PassportModule } from '#nestjs/passport';
import { AuthService } from './auth.service';
import { InternalStrategy } from './stategies/internal.strategy';
#Module({
imports: [PassportModule],
providers: [AuthService, InternalStrategy ]
})
export class AuthModule {}
This is my auth.service.ts:
import { Injectable } from '#nestjs/common';
import { Role } from 'src/workspaces/interfaces/models';
#Injectable()
export class AuthService {
validateUser(headers: Headers): any {
const workspaceId = headers['workspace-id'];
const workspaceRole = Role[headers['workspace-role']];
return {
workspaceId: workspaceId,
workspaceRole: workspaceRole
}
}
}
This is my internal.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 InternalStrategy extends PassportStrategy(Strategy, 'internal') {
constructor(private authService: AuthService) {
super({ passReqToCallback: true });
}
async validate(req: Request): Promise<any> {
console.log('Validate internal strategy')
const user = await this.authService.validateUser(req.headers);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Here is my local-auth.guard.ts:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LocalAuthGuard extends AuthGuard('internal') {}
Related
I ran into a problem with NestJS dependencies, I just started learning Nest and still don't quite understand how to build the structure correctly.
Nest can't resolve dependencies of the ChatGateway (?). Please make sure that the argument ChatAuth at index [0] is available in the ChatGateway context.
My error in terminal
chat.module.ts
`
import { Module } from '#nestjs/common';
import { ChatAuth } from './chat.middlewares';
import { ChatGateway } from './chat.gateway';
import { AuthHelper } from '../auth/auth.helper';
import { JwtStrategy } from '../auth/auth.strategy';
#Module({
imports: [ChatGateway, ChatAuth],
controllers: [],
providers: [AuthHelper, JwtStrategy],
})
export class ChatModule {}
`
chat.gateway.ts
`
import {
SubscribeMessage,
WebSocketGateway,
OnGatewayInit,
WebSocketServer,
OnGatewayConnection,
OnGatewayDisconnect,
MessageBody,
} from '#nestjs/websockets';
import { Logger } from '#nestjs/common';
import { Socket, Server } from 'socket.io';
import { ChatAuth } from './chat.middlewares';
#WebSocketGateway(7000)
export class ChatGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
#WebSocketServer() server: Server;
private chatAuthHelper: ChatAuth;
private logger: Logger = new Logger('ChatGateway');
constructor(chatAuthHelper: ChatAuth) {
this.chatAuthHelper = chatAuthHelper;
}
#SubscribeMessage('msgToServer')
handleMessage(client: Socket, payload: string): void {
console.log(payload);
this.server.emit('msgToClient', payload);
}
#SubscribeMessage('events')
handleEvent(#MessageBody() data: string): void {
const parsed = JSON.parse(JSON.stringify(data));
parsed.msg = parsed.msg + ' 3';
this.server.emit('onMessage', {
msg: 'New message',
content: parsed.msg,
});
}
afterInit(server: Server) {
this.logger.log('Init');
}
handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
if (client.handshake.headers.authorization) {
const guard = this.chatAuthHelper.use(
client.handshake.headers.authorization,
);
}
this.logger.log(`Client connected: ${client.id}`);
}
}
`
chat.middlewares.ts
`
import { Injectable, NestMiddleware } from '#nestjs/common';
import { AuthHelper } from '../auth/auth.helper';
#Injectable()
export class ChatAuth implements NestMiddleware {
private helper: AuthHelper;
constructor(helper: AuthHelper) {
this.helper = helper;
}
public async use(token): Promise<object> {
const currentToken = token.split(' ')[1];
const user = await this.helper.validate(currentToken);
console.log(JSON.stringify(user));
return user;
}
}
`
app.module.ts
`
import { Module } from '#nestjs/common';
import * as path from 'path';
import { ConfigModule } from '#nestjs/config';
import { TypeOrmModule } from '#nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { getEnvPath } from './common/helper/env.helper';
import { TypeOrmConfigService } from './shared/typeorm/typeorm.service';
import { ApiModule } from './api/api.module';
import { ChatModule } from './api/chat/chat.module';
const getPathConfig: string = path.join(__dirname, '..', 'env');
const envFilePath: string = getEnvPath(getPathConfig);
#Module({
imports: [
ConfigModule.forRoot({ envFilePath, isGlobal: true }),
TypeOrmModule.forRootAsync({ useClass: TypeOrmConfigService }),
ApiModule,
ChatModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
`
Swapped ChatAuth / ChatModule imports
come on, read the docs :D
ChatAuth is not a module, then there's no reason it to be listed in the imports array.
the page https://docs.nestjs.com/websockets/gateways shows that the gateway should be in the providers array. Again, ChatGateway is not a module, then why did you put that into imports array? the docs are pretty clear on what is the role of each option of #Module({}).
I am a new coder with nestjs, I want to use passport-jwt , nestjs/passport and firebase to build my app's authetication part, below are my codes. But I just got http 401 response, how can i fix it ?
here is my strategy:
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { getAuth } from 'firebase-admin/auth';
#Injectable()
export class FirebaseStrategy extends PassportStrategy(Strategy, 'firebase') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKeyProvider: async (request, rawJwtToken, done) => {
try {
const decodedToken = await getAuth().verifyIdToken(rawJwtToken);
done(null, decodedToken);
} catch (error) {
done(error);
}
},
});
}
async validate(payload: any) {
console.log('validate');
return payload;
}
}
here is my guard:
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class FirebaseAuthGuard extends AuthGuard('firebase') {}
here is my 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 { JwtModule } from '#nestjs/jwt';
import { FirebaseStrategy } from './firebase.stragety';
import { AuthController } from './auth.controller';
#Module({
controllers: [AuthController],
imports: [UsersModule, PassportModule, JwtModule.register({})],
providers: [AuthService, FirebaseStrategy],
exports: [AuthService],
})
export class AuthModule {}
and here is my controller:
import { Controller, Get, Request, UseGuards } from '#nestjs/common';
import { AuthService } from './auth.service';
import { FirebaseAuthGuard } from './firebase.guard';
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#UseGuards(FirebaseAuthGuard)
#Get('login')
async logIn(#Request() req) {
return 'login';
}
}
I just found my validate method in FirebaseStrategy not invoked, it should be invoked everytime when secretOrKeyProvider verified jwt in http header, isn't it ?
If you're getting a 401 with no call of the validate method of your JwtStrategy that means that for some reason passport is reading the JWT you send as invalid. To find the specific reason for it you can modify your FirebaseAuthGuard to have the following handleRequest method, which will log out extra details
handleRequest(err, user, info, context, status) {
console.log({ err, user, info, context, status });
return super.handleRequest(err, user, info, context, status);
}
As mentioned, this will print out what error or info passport is reading and throwing for, and will allow you to properly adjust your JWT/know to refresh it/change it because of invalid signature/whatever else.
Used "#nestjs/typeorm": "^9.0.1", "typeorm": "^0.3.9"
when using the withRepository method, an exception is thrown
ERROR [ExceptionsHandler] dataSource.createEntityManager is not a function
TypeError: dataSource.createEntityManager is not a function
import { Injectable } from "#nestjs/common";
import { DataSource } from "typeorm";
#Injectable()
export class BarService {
constructor(
private readonly barRepository: BarRepository,
private readonly dataSource: DataSource
) {}
async foo() {
await this.dataSource.manager.transaction((manager) => {
const barRepository = manager.withRepository(this.barRepository);
return;
});
}
}
import { Injectable } from '#nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { BarEntity } from '../entities/bar.entity';
#Injectable()
export class BarRepository extends Repository<BarEntity> {
constructor(dataSource: DataSource) {
super(BarEntity, dataSource.createEntityManager());
}
}
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { BarEntity } from './entities/bar.entity';
import { BarService } from './services/bar.service';
import { BarRepository } from './repositories/bar.repository';
#Module({
imports: [TypeOrmModule.forFeature([BarEntity])],
providers: [
BarService,
BarRepository
],
exports: [BarService],
})
export class BarModule {}
error at debbuger
I've implemented passport + jwt in my simple nestjs app for authentication. it's working fine. Now I want to use role-based authentication for routes but in my role.guard.ts , req.user is undefined. Please help me solve this issue.
auth.guard.ts
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
jwt.strategy.ts
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UserService } from 'src/user/user.service';
import { AuthService } from './auth.service';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
public authService: AuthService,
public configService: ConfigService,
public userService: UserService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
// passReqToCallback: true,
// secretOrKey: authService.getConfig('JWT_SECRET'),
});
}
async validate(payload: any) {
const user = await this.userService.getOne(payload._id);
console.log('user', user);
return user;
// return { id: payload._id };
}
}
user.controller.ts
#Controller('users')
export class UserController {
constructor(private userService: UserService) {}
#Get('/')
#UseGuards(AuthGuard('jwt'))
#UseGuards(RolesGuard)
getAllUsers(): any {
return this.userService.getAll();
}
}
role.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import * as jwt from '#nestjs/jwt';
import { promisify } from 'util';
#Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
const req = context.switchToHttp().getRequest();
console.log('req.user', req.user);
return true;
}
}
Don't use two #UseGuards(), use a single one and order the guards in the order you want them to execute:
#UseGuards(AuthGuard('jwt'), RolesGuard)
I need help with processing after authentication using Nest.js
here do I pass the failureRedirect option for passport-local when using Nest.js for authentication?
Without Nest.js
app.post('/login', passport.authenticate('local', {
//Passing options here.
successRedirect: '/',
failureRedirect: '/login'
}));
My code is. (with Nest.js)
local.strategy.ts
import { Injectable, UnauthorizedException } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import { Strategy } from "passport-local";
import { AuthService } from "./auth.service";
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
//I tried passing the option here. but failed.
})
}
async validate(username: string, password: string): Promise<string | null> {
const user = this.authService.validate(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
local.guard.ts
import { Injectable } from "#nestjs/common";
import { AuthGuard } from "#nestjs/passport";
#Injectable
export class LocalAuthGuard extends AuthGuard('local') {}
auth.controller.ts
import { Controller, Get, Post, Render, UseGuards } from "#nestjs/common";
import { LocalAuthGuard } from "./local.guard";
#Controller()
export class AuthController {
#Get("/login")
#Render("login")
getLogin() {}
//Redirect to '/login' when authentication failed.
#UseGuards(LocalAuthGuard)
#Post("/login")
postLogin() {}
}
auth.module.ts
import { Module } from "#nestjs/common";
import { PassportModule } from "#nestjs/passport";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { LocalStrategy } from "./local.strategy";
import { LocalAuthGuard } from "./local.guard";
#Module({
controllers: [AuthController],
imports: [PassportModule],
providers: [AuthService, LocalStrategy, LocalAuthGuard]
})
export class AuthModule {}
I tried adding code to AuthController#postLogin to redirect on login failure, but the code seems to run only on successful login.
I would like to redirect to the login page again in case of login failure with the failureRedirect option of passport-local.
I found a workaround since using the passport options sadly didn't work:
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
getAuthenticateOptions(context: ExecutionContext): IAuthModuleOptions {
return {
successReturnToOrRedirect: '/',
failureRedirect: '/login',
};
}
}
Instead I created a Nestjs Filter to catch an exception containing a redirect URL.
redirecting.exception.ts
export class RedirectingException {
constructor(public url: string) {}
}
redirecting-exception.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter } from '#nestjs/common';
import { Response } from 'express';
import { RedirectingException } from './redirecting.exception';
#Catch(RedirectingException)
export class RedirectingExceptionFilter implements ExceptionFilter {
catch(exception: RedirectingException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
response.redirect(exception.url);
}
}
In my validate method I'm throwing the RedirectingException with the correct error msg, e.g.
throw new RedirectingException('/login?error="User not found"');
And the controller handles the rest of the redirecting and passes the error to the view, so it can be displayed:
#Get('/login')
#Render('login.pug')
#Public()
async login(#Query() query) {
return { error: query.error };
}
#Post('/login')
#Public()
#UseGuards(LocalAuthGuard)
#Redirect('/')
async doLogin() {}
I'd rather use the passport functionality including the failureFlash, but I couldn't get it to work.