I have a controller that uses custom interceptor:
Controller:
#UseInterceptors(SignInterceptor)
#Get('users')
async findOne(#Query() getUserDto: GetUser) {
return await this.userService.findByUsername(getUserDto.username)
}
I have also I SignService, which is wrapper around NestJwt:
SignService module:
#Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
privateKey: configService.get('PRIVATE_KEY'),
publicKey: configService.get('PUBLIC_KEY'),
signOptions: {
expiresIn: configService.get('JWT_EXP_TIME_IN_SECONDS'),
algorithm: 'RS256',
},
}),
inject: [ConfigService],
}),
],
providers: [SignService],
exports: [SignService],
})
export class SignModule {}
And Finally SignInterceptor:
#Injectable()
export class SignInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(map(data => this.sign(data)))
}
sign(data) {
const signed = {
...data,
_signed: 'signedContent',
}
return signed
}
}
SignService works properly and I use it. I would like to use this as an interceptor
How can I inject SignService in to SignInterceptor, so I can use the functions it provides?
I assume that SignInterceptor is part of the ApiModule:
#Module({
imports: [SignModule], // Import the SignModule into the ApiModule.
controllers: [UsersController],
providers: [SignInterceptor],
})
export class ApiModule {}
Then inject the SignService into the SignInterceptor:
#Injectable()
export class SignInterceptor implements NestInterceptor {
constructor(private signService: SignService) {}
//...
}
Because you use #UseInterceptors(SignInterceptor) to use the interceptor in your controller Nestjs will instantiate the SignInterceptor for you and handle the injection of dependencies.
Related
AppModule
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: !!ENV ? `.env.${ENV}` : '.env',
}),
AedesModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
port: 1883,
}),
inject: [ConfigService],
}),
AuthModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
My AEDES MODULE:
import { DynamicModule, Global, Module, Provider } from '#nestjs/common';
import { AedesModuleAsyncOptions, AedesModuleOptions } from './#types/package';
import { AedesService } from './aedes.service';
#Global()
#Module({
providers: [AedesService],
exports: [AedesService],
})
export class AedesModule {
public static forRootAsync(options: AedesModuleAsyncOptions): DynamicModule {
const customOptions = this.getCustomOptions(options);
return {
module: AedesModule,
imports: options.imports || [],
providers: [customOptions, this.aedesProvider],
exports: [customOptions, this.aedesProvider],
};
}
private static getCustomOptions(options: AedesModuleAsyncOptions): Provider {
return {
provide: 'AEDES_OPTIONS',
useFactory: options.useFactory,
inject: options.inject || [],
};
}
private static aedesProvider: Provider = {
provide: AedesService,
useFactory: async (options: AedesModuleOptions) => {
const aedes = new AedesService(options);
aedes.init();
return aedes;
},
inject: ['AEDES_OPTIONS'],
};
private static getOptionsProvider(options: AedesModuleOptions): Provider {
return {
provide: 'AEDES_OPTIONS',
useValue: options,
};
}
}
type definitions:
import { ModuleMetadata } from '#nestjs/common';
export interface AedesModuleAsyncOptions
extends Pick<ModuleMetadata, 'imports'> {
inject?: any[];
useFactory: (
...args: any[]
) => Promise<AedesModuleOptions> | AedesModuleOptions;
}
export interface AedesModuleOptions {
port: number;
}
My Service:
export class AedesService {
public broker: aedes;
private port: number;
constructor(options: AedesModuleOptions) {
this.port = options.port;
}
init() {
this.broker = new aedes({
authenticate: async (client, username, password, done) => {
console.log(username, password);
const decoded: any =
await this.authService.manualAccessTokenVerification(
password.toString(),
); // I can't inject or import the existing authService which importing from another module.
console.log(decoded);
return done(null, true);
},
});
const mqttServer = createServer(this.broker);
mqttServer.listen(this.port, () => {
Logger.log(`MQTT server listening on ${this.port}`);
});
}
}
I can't inject or import the existing authService which importing from another module. Is it possible?
i want know how to inject ConfigService in PassportModule AuthGuard with custom params?
here is my code
auth.module.ts
#Module({
imports: [
PassportModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService): Promise<IAuthModuleOptions> => {
return {}
}
}),
],
// ...
})
export class AuthModule {}
github-auth.guard.ts
#Controller('auth')
export class AuthController {
// ...
#Get('github-oauth')
#UseGuards(new GithubAuthGuard({}))
async githubOAuth(#Req() req: Request, #Res() res: Response) {
const user = req.user
return await this.handleOauth(user, req, res)
}
}
github-auth.guard.ts
import { ConfigService } from '#nestjs/config'
import { AuthGuard } from '#nestjs/passport'
#Injectable()
export class GithubAuthGuard extends AuthGuard('github') {
constructor(private readonly configService: ConfigService) {
super()
}
//...
}
i call the UseGuards with new GithubAuthGuard({}) because i want pass custom params.
Update:
AuthGuard('github') return a wraped class can accept options the then pass down to my custom strategy's authenticate function as the second argument.
here is my github.strategy.ts
import { Strategy } from 'passport'
class StrategyFoo extends Strategy {
constructor(options, verify) {
//...
}
// options from AuthGuard('github')
authenticate(req, options) {
const self = this
const redirect_uri = options.callbackURL || this._options.callbackURL
// ...
}
}
#Injectable()
export class GithubBarStrategy extends PassportStrategy(StrategyFoo, 'github') {
//...
}
export const GithubStrategy = GithubBarStrategy
after some research i figure it out
Nest will not inject anything on a manually instantiated class
so just call #UseGuards(GithubAuthGuard) and then inject ConfigService in github-auth.guard.ts or github.strategy.ts like:
inject in github-auth.guard.ts
#Injectable()
export class GithubAuthGuard extends AuthGuard('github') {
// #Inject(ConfigService)
// private configService: ConfigService
constructor(private readonly configService: ConfigService) {
//
const options = {
callbackURL: configService.get('OAuth.github.callbackURL'),
//...
}
super(options)
}
getAuthenticateOptions(context) {
// this also works
return {
callbackURL: this.configService.get('OAuth.github.callbackURL'),
//...
}
}
// ...
}
or
inject in github.strategy.ts
class StrategyFoo extends Strategy {
private _options: any
constructor(options, verify) {
//...
this._options = options
// ...
}
authenticate(req, options) {
// ...
let _options = {
...options,
...this._options
}
// ...
}
}
#Injectable()
export class GithubBarStrategy extends PassportStrategy(StrategyFoo, 'github') {
constructor(private readonly configService: ConfigService) {
super({
passReqToCallback: true,
callbackURL: this.configService.get('OAuth.github.callbackURL'),
// ...
})
}
// ...
}
I have an external service providing a JWT token. In Nestjs i first have JwtGuard class:
#Injectable()
export class JwtGuard extends AuthGuard('JWT_STRATEGY') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
console.log('JwtGuard');
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
and then a passport strategy:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'JWT_STRATEGY') {
constructor(private configService: ConfigService) {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: configService.get<string>('ADFS_KEYS_URL'),
}),
ignoreExpiration: false,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: configService.get<string>('ADFS_AUDIENCE'),
issuer: configService.get<string>('ADFS_ISSUER'),
algorithms: ['RS256'],
});
}
validate(payload: unknown): unknown {
console.log('jwt strategy');
console.log(payload);
return payload;
}
}
It seems that JwtGuard is running first, then the strategy. But if i want to do additional guards and checks, say for roles. Where does one do that? Do i need another guard that runs after the passport strategy? I have two roles "User" and "Admin".
First of all, define a global guard (called RolesGuard) in the AppModule as following:
providers: [
AppService,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
]
Then within RolesGuard we have the following:
export enum FamilyRole {
Admin = 'Admin',
User = 'User',
}
...
export class FamilyRolesGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const requiredRoles = this.reflector.getAllAndOverride<FamilyRole>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
// do the rest and return either true or false
}
}
Then create your own decorator and you can decorate your APIs if you need that API to protect your app based on your guard.
import { SetMetadata } from '#nestjs/common';
export const ROLES_KEY = 'FamilyRoles';
export const FamilyRoles = (...roles: FamilyRole[]) =>
SetMetadata(ROLES_KEY, roles);
Then you can use your decorator in your API like this:
#Post('user')
#FamilyRoles(FamilyRole.Admin)
...
So, in your API, if you won't have FamilyRoles, in the guard you won't have requiredRoles, and the if block will return true.
More info: https://docs.nestjs.com/security/authorization
I'm trying here to implement a token-based authentication using the passport-headerapikey library.
This is what I've tried so far, and for some reason I have a 500 server error popping from somewhere I couldn't find.
This is the structure of my authentication system (I also have a JWT-token based strategy in parallel on my graphQL queries).
app.module
#Module({
imports: [
AuthModule,
],
controllers: [AppController],
providers: [
AppService
],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('/datasource/:id');
}
}
auth.module
#Module({
imports: [
PassportModule,
],
providers: [
AuthService,
DatasourceTokenStrategy,
],
controllers: [],
exports: [AuthService],
})
export class AuthModule {}
datasourceToken.strategy
#Injectable()
export class DatasourceTokenStrategy extends PassportStrategy(
HeaderAPIKeyStrategy,
'datasourceToken',
) {
constructor(private authService: AuthService) {
super(
{ header: 'datasourceToken', prefix: '' },
true,
(apikey, done, req) => {
const checkKey = authService.validateDatasourceToken(apikey);
if (!checkKey) {
return done(false);
}
return done(true);
},
);
}
}
authMiddleware.strategy
import {
Injectable,
NestMiddleware,
UnauthorizedException,
} from '#nestjs/common';
import * as passport from 'passport';
#Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
passport.authenticate(
'datasourceToken',
{ session: false, failureRedirect: '/api/unauthorized' },
(value) => {
if (value) {
next();
} else {
throw new UnauthorizedException();
}
},
)(req, res, next);
}
}
This is the error thrown when testing the endpoint with Jest:
When running my debug mode, I can see that the datasourceToken strategy is ok (I can retrieve the datasourceToken properly and validate it), but I think the problem is happening after my auth middleware..
Thanks guys for your insights
The function "done()" takes 3 arguments.
done(error, user, info)
You need to pass null as the first argument to let passport know that there was no error while authenticating.
done(null, true)
I've a AuthGuard who check the JWT token in controllers. I want use this Guard in controllers to check authentication. I've this error:
Nest can't resolve dependencies of the AuthGuard (?, +). Please make sure that the argument at index [0] is available in the current context.
TestController.ts
import {
Controller,
Post,
Body,
HttpCode,
HttpStatus,
UseInterceptors,
UseGuards,
} from "#nestjs/common";
import { TestService } from "Services/TestService";
import { CreateTestDto } from "Dtos/CreateTestDto";
import { ApiConsumes, ApiProduces } from "#nestjs/swagger";
import { AuthGuard } from "Guards/AuthGuard";
#Controller("/tests")
#UseGuards(AuthGuard)
export class TestController {
constructor(
private readonly testService: TestService,
) {}
#Post("/create")
#HttpCode(HttpStatus.OK)
#ApiConsumes("application/json")
#ApiProduces("application/json")
async create(#Body() createTestDto: CreateTestDto): Promise<void> {
// this.testService.blabla();
}
}
AuthGuard.ts
import { CanActivate, ExecutionContext, Injectable } from "#nestjs/common";
import { AuthService } from "Services/AuthService";
import { UserService } from "Services/UserService";
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
) {}
async canActivate(dataOrRequest, context: ExecutionContext): Promise<boolean> {
try {
// code is here
return true;
} catch (e) {
return false;
}
}
}
AuthService (the dependency that could not be resolved) must be available in the scope containing the controller which uses the guard.
What does it mean?
Include AuthService in the providers of the module loading your controller.
e.g.
#Module({
controllers: [TestController],
providers: [AuthService, TestService, UserService],
})
export class YourModule {}
EDIT - Forgot to mention that an other clean way (maybe cleaner, depending on the context) consists in importing the module that offers (exports) the service.
e.g.
#Module({
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}
#Module({
imports: [AuthModule],
controllers: [TestController],
providers: [TestService, UserService],
})
export class YourModule {}