I create an authentication middleware in NestJs like below:
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor() {}
async use(req: any, res: any, next: () => void) {
const authHeaders = req.headers.authorization;
if (authHeaders) {
//some logic etc.
//req.user = user;
next();
} else {
throw new UnathorizedException();
}
}
}
... where I get from headers - an auth token, decode it and check if this user is correct and exists in database, if he exists then i set user object into req.user. And now I have a question, how to get this req.user in my services and use in business logic? I need to get id from req.user but I do not know how.
I know that I can do this by using #Req() request in controller parameters and pass this request into my function, but I do not want it, cause is (for me) a ugly practice. So, how to get this req.user into my services?
thanks for any help!
Well, to get the user in the service you have two options:
use #Req() in the controller and pass it, as you have mentioned
Make your service REQUEST scoped and inject the request object into the service
Personally, I'd go with the former, as request scoping has its own pros and cons to start weighing and dealing with (like not being able to use the service in a passport strategy or a cron job). You can also just make the user optional, or bundle it into the body or whatever is passed to the service and then have access to it without it being an explicit parameter.
You can create a decorator to do it. Something like this
current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const CurrentUser = createParamDecorator(
(property: string, ectx: ExecutionContext) => {
const ctx = ectx.getArgByIndex(1);
return property ? ctx.req.user && ctx.req.user[property] : ctx.req.user;
},
);
example.controller.ts
#ApiTags('example')
#Controller('example')
export class ExampleController {
constructor(private readonly exampleService: ExampleService) {}
#Get('/')
public async doSomething(#CurrentUser() user: YourUserClassOrInteface,): Promise<any> {
return this.exampleService.exampleFunction(user.id);
}
}
example.service.ts
export class ExampleService {
constructor() {}
public async exampleFunction(id: string): Promise<void> {
console.log('id:', id);
return;
}
}
IMPORTANT: Injecting the Request in the services is not a good solution because it will make a new one in each endpoint request. That is why the Decorators are used. It will make it easy to work with needed data and do not hand over only the parameters that are needed instead of transferring the extra big request object.
Alternative solution(if you won't use request scoped injection): you can use async hooks. There is many libraries which simplify async hooks usage, like this one. You simply set your context in middleware:
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor() {}
async use(req: any, res: any, next: () => void) {
const authHeaders = req.headers.authorization;
if (authHeaders) {
//some logic etc.
//req.user = user;
Context.run(next, { user: req.user });
} else {
throw new UnathorizedException();
}
}
}
And then you can get user instance in any place in your code by simply calling Context.get()
You can define your own Request interface like this
import { Request } from 'express';
...
export interface IRequestWithUser extends Request {
user: User;
}
then just give the type of req parameter to IRequestWithUser.
Related
I have the below two guards in NestJS(one for api key based authentication and another for token based authentication).The ApiKeyGuard is the top priority.I want to implement a system where if anyone has a key it will not check the other guard.Is there any way I can make the AuthGuard optional based on whether the first Guard passed in cases where there is a ApiKeyGuard?
// Can be accessed with token within app as well as third party users
#UseGuards(ApiKeyGuard, AuthGuard)
#Get('/get-products')
async getProducts(): Promise<any> {
try {
return this.moduleRef
.get(`appService`, { strict: false })
.getProducts();
} catch (error) {
throw new InternalServerErrorException(error.message, error.status);
}
}
// Only to be accessed with token within app
#UseGuards(AuthGuard)
#Get('/get-users')
async getUsers(): Promise<any> {
try {
return this.moduleRef
.get(`appService`, { strict: false })
.getUsers();
} catch (error) {
throw new InternalServerErrorException(error.message, error.status);
}
}
The below guard is used to check for api key based authentication
api-key.guard.ts
#Injectable()
export class ApiKeyGuard implements CanActivate {
constructor(private readonly apiKeyService: ApiKeyService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const key = req.headers['X-API-KEY'] ?? req.query.api_key;
return this.apiKeyService.isKeyValid(key);
}
The below guard is used to check for token based authentication
authentication.guard.ts
#Injectable()
export class AuthGuard implements CanActivate, OnModuleInit {
constructor(private readonly moduleRef: ModuleRef) {}
onModuleInit() {}
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
// Get request data and validate token
const request = context.switchToHttp().getRequest();
if (request.headers.authorization) {
const token = request.headers.authorization.split(' ')[1];
const response = await this.checkToken(token);
if (response) {
return response;
} else {
throw new UnauthorizedException();
}
} else {
throw new UnauthorizedException();
}
} catch (error) {
throw new UnauthorizedException();
}
}
}
What I did was using #nestjs/passport and using the AuthGuard and making custom PassportJS strategies. I had a similar issue, and looked for a way to accomplish this without using some "magic". The documentation can be found here.
In the AuthGuard, you can add multiple guards. It's a bit hidden away in the documentation, although it is very powerful. Take a look here, especially the last line of the section, it states:
In addition to extending the default error handling and authentication logic, we can allow authentication to go through a chain of strategies. The first strategy to succeed, redirect, or error will halt the chain. Authentication failures will proceed through each strategy in series, ultimately failing if all strategies fail.
Which can be done like so:
export class JwtAuthGuard extends AuthGuard(['strategy_jwt_1', 'strategy_jwt_2', '...']) { ... }
Now, back to your example, you've to create 2 custom strategies, one for the API key and one for the authorization header, and both these guards should be activated.
So for the API strategy (as example):
import { Strategy } from 'passport-custom';
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy } from 'passport-custom';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
#Injectable()
export class ApiStrategy extends PassportStrategy(Strategy, 'api-strategy') {
constructor(private readonly apiKeyService: ApiKeyService) {}
async validate(req: Request): Promise<User> {
const key = req.headers['X-API-KEY'] ?? req.query.api_key;
if ((await this.apiKeyService.isKeyValid(key)) === false) {
throw new UnauthorizedException();
}
return this.getUser();
}
}
Do something similar for your other way of authenticating, and then use the Passport guard as follows:
#UseGuard(AuthGuard(['api-strategy', 'other-strategy'])
This way, the guard will try all strategies (in order) and when all of them fail, your authentication has failed. If one of them succeeds, you're authenticated!
I have created a guard
#Injectable()
export class EmailConfirmationGuard implements CanActivate {
canActivate(context: ExecutionContext) {
const request: RequestWithUser = context.switchToHttp().getRequest();
console.log(request.user);
if (!request.user?.hasEmailConfirmed) {
throw new UnauthorizedException("Confirm your email first before updating your profile");
}
return true;
}
}
And i am using it on of my endpoints
#UseGuards(JwtAuthGuard)
#UseGuards(EmailConfirmationGuard)
#Post("/update-profile")
#UseInterceptors(FileInterceptor("file"))
async updateProfile(#UploadedFile() file: Express.Multer.File, #Body("full-name") fullname: string,#Request() req) {
The point is it is faling because getRequest is not returning the authenticated user it is returning undefined
const request: RequestWithUser = context.switchToHttp().getRequest();
How can i return the authenticated user from the response ?
You should use your JwtAuthGuard at your controller level since nest doesn't have an order to run the decorators.
#UseGuards(JwtAuthGuard)
export class YourController{
#UseGuards(EmailConfirmationGuard)
#Post()
public async yourFunction() {}
}
So I am currently using NestJS extensively in my organization. And for authentication purposes we are using our own guards. So my question is that can anyone please guide me if there any way to pass data from guard to the controller, other than response.locals of expressjs? This is creating an hard dependency on the framework and I don't want that at this moment.
TIA.
The only ways possible to pass data from a Guard to a Controller is to either attach the data to a field on the request or to use some sort of metadata reflection, which may become more challenging than it is worth.
In your guard you could have a canActivate function like
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const req = context.switchToHttp().getRequest();
if (/* some logic with req */) {
req.myData = 'some custom value';
}
return true;
}
And in your controller you could then pull req.myData and get the some custom value string back.
Instead of using the Guard, you can create your custom decorator to get the data:
export const Authorization = createParamDecorator((_, request: any) => {
const { authorization: accessToken } = request.headers;
try {
const decoded = jwt.verify(accessToken, process.env.JWT_HASH);
return pick(decoded, 'userId');
} catch (ex) {
throw new InvalidToken();
}
});
export interface AuthUser {
userId: string;
}
And pass to your controller like this:
#Post()
createFeedback(
#Body() body: FeedbackBody,
#Authorization() user: AuthUser,
): Promise<Feedback> {
body.userId = user.userId;
return this.feedbackService.feedback(body, user);
}
This can act as a guard because when your token is invalid, it will throw an exception
as other guys said you can pass data on the request object on the guard but receiving them through the #Req decorator is not fancy, especially if you don't have any other use for the request object. You can create a custom decorator that will retrieve the data you want and inject them into any controller
I want to create a NestJs app and want to have a middleware validating the token in the request object and a authentication guard validating the user in the token payload.
By splitting this I was hoping to have a clean separation. First my middleware
#Injectable()
export class TokenMiddleware implements NestMiddleware {
use(req: any, res: Response, next: NextFunction) {
try {
const headers: IncomingHttpHeaders = req.headers;
const authorization: string = headers.authorization;
const bearerToken: string[] = authorization.split(' ');
const token: string = bearerToken[1];
// !! Check if token was invalidated !!
req.token = token;
req.tokenPayload = verifyToken(token);
next();
} catch (error) {
throw new UnauthorizedException();
}
}
}
It only validates the token and extends the request object with the encoded token and its payload. My auth guard
#Injectable()
export class AuthenticationGuard implements CanActivate {
constructor(private readonly usersService: UsersService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request: any = context.switchToHttp().getRequest();
try {
const user: any = request.tokenPayload;
if (!user) {
throw new Error();
}
const findByIdDTO: FindByIdDTO = { id: user.id };
const existingUser: UserRO = await this.usersService.findById(findByIdDTO);
if (!existingUser) {
throw new Error();
}
// attach the user to the request object?
return true;
} catch (error) {
throw new UnauthorizedException();
}
}
}
This guard checks if the provided user in the tokenpayload is a valid one. If everything is fine, where should I attach the user to the request object? As far as I know the guard only checks if something is correct. But I don't want to keep all this logic in the token middleware. Where can I attach the database user to the request after finishing the validation in the auth guard?
If you want to do something similar to Passport you could always attach the user to req.user, which is seen as a pretty standard ting in the Node.JS world.
Side question for you: any reason to not have two guards that function right after another? Have one guard for checking that the token is there and is indeed a valid token and one for validating the user on the token is indeed a valid on. That way you don't use a middleware (which is kind of included mostly for the sake of compatibility) and still have the separated logic.
Where can I attach the database user to the request after finishing the validation in the auth guard?
I believe that Guard, as you noticed, should validate if given user has the right to use given method.
Depending on your needs, you can go into different paths:
1) use passport and a strategy to do what you need (https://stackoverflow.com/a/57929429/4319037 I wrote a few words and lines about this already). Furthermore, it will already cover most of the code you have to extract the token.
#Injectable()
export class HttpStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super()
}
async validate(token: string) {
const user = await this.authService.findUserByToken(token);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
2) use Interceptor on controller/method level to attach the user to given request (and throw if token is missing); your Guard will receive the user already, thus you can validate if the user has correct role/rights to execute the method.
Please let me know if I misunderstood what you want to achieve or need more details on particular way, thanks!
I am struggling with Typescript and modifying the definition of existing module.
We are used to put anything we want to output to "res.out" and at the end there is something like this "res.json(res.out)". This allows us to have general control over the app at the moment of sending the response.
So I have function like this
export async function register(req: Request, res: Response, next: Next) {
try {
const user = await userService.registerOrdinaryUser(req.body)
res.status(201);
res.out = user;
return helper.resSend(req, res, next);
} catch (ex) {
return helper.resError(ex, req, res, next);
}
};
We are using restify. And I get compilation error, because "out" is not part of restify.Response.
Now we have workaround that we have our "own" objects, that extends the Restify ones.
import {
Server as iServer,
Request as iRequest,
Response as iResponse,
} from 'restify'
export interface Server extends iServer {
}
export interface Request extends iRequest {
}
export interface Response extends iResponse {
out?: any;
}
export {Next} from 'restify';
We just did this to make project compilable, but looking for better solution. I have tried things like this:
/// <reference types="restify" />
namespace Response {
export interface customResponse;
}
interface customResponse {
out?: any;
}
But it does not work, right now it says "Duplicate identifier 'Response'".
So anyone how to add definition to restify.Response object with some simple code?
You can use interface merging.
import { Response } from "restify";
declare module "restify" {
interface Response {
out?: any
}
}