How to pass api key as query string on request url using passport and nestjs - passport.js

I have developed api-key strategy following https://www.stewright.me/2021/03/add-header-api-key-to-nestjs-rest-api/
and it works, I pass api-key in header and it authorize it.
Now for some cases I need to pass api-key as query params to url instead of header. I wasn't able to figure it out.
example mysite.com/api/book/5?api-key=myapikey
my current code is
api-key-strategy.ts
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') {
constructor(private configService: ConfigService) {
super({ header: 'api-key', prefix: '' }, true, async (apiKey, done) =>
this.validate(apiKey, done)
);
}
private validate(apiKey: string, done: (error: Error, data) => any) {
if (
this.configService.get(AuthEnvironmentVariables.API_KEY) === apiKey
) {
done(null, true);
}
done(new UnauthorizedException(), null);
}
}
api-key-auth-gurad.ts
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class ApiKeyAuthGuard extends AuthGuard('api-key') {}
app.controller
...
#UseGuards(ApiKeyAuthGuard)
#Get('/test-api-key')
testApiKey() {
return {
date: new Date().toISOString()
};
}
...

I found a solution in case someone else has same problem.
I added canActivate method to my guard, then read the api key from request.query, and add it to header. Then the rest of code is working as before and checking header
#Injectable()
export class ApiKeyAuthGuard extends AuthGuard('api-key') {
canActivate(context: ExecutionContext) {
const request: Request = context.switchToHttp().getRequest();
if (request && request.query['api-key'] && !request.header('api-key')) {
(request.headers['api-key'] as any) = request.query['api-key'];
}
return super.canActivate(context);
}
}

Related

How can I return a non empty response using Nest JS api and postman

I am using Nest Js to setup my server. I tried to fetch data from Postman to test if the API urls work. However I find that I get empty response from the server or undefined value from the postman request. The below code is the users.controller.ts
import {
Body,
Controller,
Delete,
Get,
Header,
Param,
Post,
Put,
} from '#nestjs/common';
import { User } from './users.entity';
import { UsersService } from './users.service';
#Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
/* #Get('/api/users')
index(): string {
return 'This will return user.';
} */
#Get()
index(): Promise<User[]> {
return this.usersService.findUsers();
}
#Post('create')
#Header('Content-Type', 'application/json')
async create(#Body() userData: User): Promise<any> {
console.log(userData.UserId, userData.UserType + ' Check if working');
return this.usersService.create(userData);
}
#Put(':userid/update')
async update(#Param('userid') userid, #Body() userData: User): Promise<any> {
userData.UserId = Number(userid);
console.log('Update #' + userData.UserId);
return this.usersService.update(userData);
}
#Delete(':userid/delete')
async delete(#Param('userid') userid): Promise<any> {
return this.usersService.delete(userid);
}
}
and this code is users.service.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './users.entity';
import { UpdateResult, DeleteResult } from 'typeorm';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(User) private userRepository: Repository<User>,
) {}
async findUsers(): Promise<User[]> {
return await this.userRepository.find();
}
async create(user: User): Promise<User> {
console.log(user.UserType + 'User type');
return await this.userRepository.save(user);
}
async update(user: User): Promise<UpdateResult> {
return await this.userRepository.update(user.UserId, user);
}
async delete(userid): Promise<DeleteResult> {
return await this.userRepository.delete(userid);
}
}
If you can see the post request, there is a console.log() to check whether there is a response or not. Hence I am getting an undefined value instead. I am seeking for support to understand where I am going wrong. I am not able to traceback the error as well.
Welcome!
You are not logging what your service returns, instead you are logging what your service receives as the body of the request:
#Post('create')
#Header('Content-Type', 'application/json')
async create(#Body() userData: User): Promise<any> {
// ##################################
// here, you are logging userData, which is the object your
// service receives as input
// ##################################
console.log(userData.UserId, userData.UserType + ' Check if working');
return this.usersService.create(userData);
}
If that console.log line is logging undefined, it means you are not sending the right data in your HTTP Post request body in postman.
You should be sending a User json object as the request payload.
Had the same problem and solved it by annotating my return object.
export class CriarTarefaDto {
public nome: string;
public ativo?: boolean;
}
This was my object that always returned me empty when checking the return of the #Body annotation. I fixed it by including class-validator lib annotations in my DTO properties.
import { IsBoolean, IsString } from 'class-validator';
export class CriarTarefaDto {
#IsString()
public nome: string;
#IsBoolean()
public ativo?: boolean;
}
In your DTO object you need to use some annotation or extend the DTO with classes that register its properties.

How to pass jwt to prisma middleware in nestjs

I am using nestjs, graphql, & prisma. I am trying to figure out how to pass my jwt token for each database request to the prisma service iv created. Iv tried an object to the constructor but then wont compile saying I am missing a dependency injection for whatever I reference in the constructor paramter.
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleDestroy {
constructor() {
super();
//TODO how do I pass my jwt token to this for each request?
this.$use(async (params, next) => {
if (params.action === 'create') {
params.args.data['createdBy'] = 'jwt username goes here';
}
if (params.action === 'update') {
params.args.data['updatedBy'] = 'jwt username goes here';
}
const result = await next(params);
return result;
});
}
async onModuleDestroy() {
await this.$disconnect();
}
}
Are you using a nest middleware?
JWT is normally passed to a Controller, not a service.
Example:
#Injectable()
export class MyMiddleware implements NestMiddleware {
private backend: any // This is your backend
constructor() {
this.backend = null // initialize your backend
}
use(req: Request, res: Response, next: any) {
const token = <string>req.headers.authorization
if (token != null && token != '') {
this.backend
.auth()
.verifyIdToken(<string>token.replace('Bearer ', ''))
.then(async (decodedToken) => {
const user = {
email: decodedToken.email,
uid: decodedToken.uid,
tenantId: decodedToken.tenantId,
}
req['user'] = user
next()
})
.catch((error) => {
log.info('Token validation failed', error)
this.accessDenied(req.url, res)
})
} else {
log.info('No valid token provided', token)
return this.accessDenied(req.url, res)
}
}
private accessDenied(url: string, res: Response) {
res.status(403).json({
statusCode: 403,
timestamp: new Date().toISOString(),
path: url,
message: 'Access Denied',
})
}
}
So every time I get an API call with a valid token, the token is added to the user[] in the request.
In my Controller Class I can then go ahead and use the data:
#Post()
postHello(#Req() request: Request): string {
return 'Hello ' + request['user']?.tenantId + '!'
}
I just learned about an update in Nest.js which allows you to easily inject the header also in a Service. Maybe that is exactly what you need.
So in your service.ts:
import { Global, INestApplication, Inject, Injectable, OnModuleInit, Scope } from '#nestjs/common'
import { PrismaClient } from '#prisma/client'
import { REQUEST } from '#nestjs/core'
#Global()
#Injectable({ scope: Scope.REQUEST })
export class PrismaService extends PrismaClient implements OnModuleInit {
constructor(#Inject(REQUEST) private readonly request: any) {
super()
console.log('request:', request?.user)
}
async onModuleInit() {
// Multi Tenancy Middleware
this.$use(async (params, next) => {
// Check incoming query type
console.log('params:', params)
console.log('request:', this.request)
return next(params)
})
await this.$connect()
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close()
})
}
}
As you can see in the log output, you have access to the entire request object.

Nestjs how to pass data from AuthGuard to controller

I have two microservices one for authentication and another for users. I can log in and get a token, and i can use protected routes only when logged in. However I want to use the userId which i get in the AuthGuard's canActivate function, but i cant reach it in the controller. What is the best way to do it?
My auth guard:
import { CanActivate, ExecutionContext, Inject, Logger } from '#nestjs/common';
import { ClientProxy } from '#nestjs/microservices';
export class JwtAuthGuard implements CanActivate {
constructor(
#Inject('AUTH_CLIENT')
private readonly client: ClientProxy,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
try {
const res = await this.client
.send(
{ role: 'auth', cmd: 'check' },
{ jwt: req.headers['authorization']?.split(' ')[1] },
)
.toPromise<boolean>();
return res;
} catch (err) {
Logger.error(err);
return false;
}
}
}
The controller:
#UseGuards(JwtAuthGuard)
#Get('greet')
async greet(#Request() req): Promise<string> {
return 'AUTHENTICATED!' + req;
}
The response:
AUTHENTICATED![object Object]
Attach the userId that you get in the AuthGuard to the req object and then you can access it in the controller:
// after fetching the auth user in the AuthGuard, attach its ID like this
req.userId = authUser.id
And in the controller, you can access it like this:
#UseGuards(JwtAuthGuard)
#Get('greet')
async greet(#Request() req): Promise<string> {
return 'AUTHENTICATED USER ID!' + req.userId;
}

How to implement multiple passport jwt authentication strategies in Nestjs

I have an existing authentication for users which is already working fine. The token for user authentication expires within an hour.
I want to implement another separate authentication strategy a third API that is consuming my Nestjs API. There are separate endpoints for the third-party API, the token should expire with 24 hours. The API has to stay connected to my app for 24 hours.
I don't mind using additional package to achieve this.
I also need to create a guard called thirdParty Guard so that the 3rd part API alone will have access to that endpoint.
This is my jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.SECRETKEY
});
}
async validate(payload: any, done: VerifiedCallback) {
const user = await this.authService.validateUser(payload);
if (!user) {
return done(
new HttpException('Unauthorised access', HttpStatus.UNAUTHORIZED),
false,
);
}
//return user;
return done(null, user, payload.iat)
}
}
ApiKey.strategy.ts
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy) {
constructor(private authService: AuthService) {
super({
header: 'api_key',
prefix: ''
}, true,
(apikey: string, done: any, req: any, next: () => void) => {
const checkKey = this.authService.validateApiKey(apikey);
if (!checkKey) {
return done(
new HttpException('Unauthorized access, verify the token is correct', HttpStatus.UNAUTHORIZED),
false,
);
}
return done(null, true, next);
});
}
}
and this is the auth.service.ts
#Injectable()
export class AuthService {
constructor(private userService: UserService) { }
async signPayLoad(payload: any) {
return sign(payload, process.env.SECRETKEY, { expiresIn: '1h' });
}
async validateUser(payload: any) {
const returnuser = await this.userService.findByPayLoad(payload);
return returnuser;
}
validateApiKey(apiKey: string) {
const keys = process.env.API_KEYS;
const apiKeys = keys.split(',');
return apiKeys.find(key => apiKey === key);
}
}
With the above setup, If you are using Passport-HeaderAPIKey then try adding headerapikey in the Guard. The below code worked for me.
Ref: NestJS extending guard
import { ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { AuthGuard as NestAuthGuard } from '#nestjs/passport';
#Injectable()
export class AuthGuard extends NestAuthGuard(['jwt', 'headerapikey']) {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
return super.canActivate(context);
}
}

Authentication & Roles with Guards/Decorators: How to pass user object?

With the help of Guards/Decorators I try to check a JWT first and then the roles a user has.
I have read the documentation regarding Authentication, Guards and Decorators and understand the principles behind them.
However, what I cannot do is to somehow make the authenticated user from JWT-Guard available to Roles-Guards.
In every example that I found, exactly this part that is interesting for me is skipped / left out...
Grateful for every tip!
This is my latest try:
jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { JwtPayload } from './jwt.model';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
passReqToCallback: true,
ignoreExpiration: false,
secretOrKey: '0000',
expiresIn: '3 days'
});
}
async validate(payload: JwtPayload) {
return {
id: payload.id,
email: payload.email,
username: payload.username
};
}
}
roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {
}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return false;
}
const request = context.switchToHttp().getRequest();
const user = request.user ??? // THIS is what is missing
return roles.some((role) => {
return role === user.role;
});
}
}
roles.decorator.ts
import { SetMetadata } from '#nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
users.controller.ts
#UseGuards(AuthGuard('jwt'))
#Roles('admin', 'member')
#Get('/')
async doSomething(#Req() req): Promise<User> {
return await this.usersService.doSomething(req.user.id);
}
Your decorator and guards look fine, but from the snippet of your users.controller.ts file it is not clear whether the roles guard is actually applied for the GET / route.
I do, however, have an NestJS app with a quite similar setup based on the guards documentation. The following code in users.controller.ts works as intended:
#UseGuards(JwtAuthGuard, RolesGuard)
#Controller('/users')
export class UserController {
constructor(private readonly userService: UserService) {}
#Get()
#Roles(UserRole.ADMIN)
public async index(): Promise<User[]> {
return this.userService.findAll();
}
// ...
}
Note how both the auth and roles guard are activated in the same scope and that JwtAuthGuard is added before RolesGuard. If I were to change the sequence of the guards then the RolesGuard would not be able to retrieve the user of the request.
Also, you might want to have a look at a similar question from some time ago which contains some details on the order of guards in different scopes.

Resources