I can't get user from request in decorator nest, pleas help me.
Middleware good working it find user by token and save user in request
my middleware:
import { Injectable, NestMiddleware, HttpStatus } from '#nestjs/common';
import { HttpException } from '#nestjs/common/exceptions/http.exception';
import { Request, Response } from 'express';
import { AuthenticationService } from '../modules/authentication-v1/authentication.service';
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor(
private readonly authenticationService : AuthenticationService
) {
}
async use(req: Request, res: Response, next: Function) {
let token = req.headers;
if(!token) {
throw new HttpException('token is required', 401);
}
if (!token.match(/Bearer\s(\S+)/)) {
throw new HttpException('Unsupported token', 401);
}
const [ tokenType, tokenValue ] = token.split(' ');
try {
const result = await this.authenticationService.getAccessToken(tokenValue);
req.user = result;
next();
} catch (e) {
throw new HttpException(e.message, 401);
}
}
}
but here request don't have property user and i don't know why
user decorator:
export const User = createParamDecorator((data: any, req) => {
return req.user; // but here user undefined
});
app module:
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthenticationMiddleware)
.forRoutes({ path: 'auto-reports-v1', method: RequestMethod.GET });
}
}
route method:
#UseInterceptors(LoggingInterceptor)
#Controller('auto-reports-v1')
#ApiTags('auto-reports-v1')
export class AutoReportsController {
constructor(private readonly autoReportsService: AutoReportsService) {}
#Get()
async findAll(
#Query() filter: any,
#User() user: any): Promise<Paginated> {
return this.autoReportsService.findPaginatedByFilter(filter, user);
}
}
In NestJS with Fastify, middleware attaches values to req.raw. This is because middleware runs before the request gets wrapped by the FastifyRequest object, so all value attachments are made to the IncomingRequest object (same as Express Request object). Fastify will then wrap the IncomingRequest in its own FastifyRequest object and expose the IncomingRequest through req.raw meaning the user you are looking for is at req.raw.user not req.user. If you want to have the same functionality across Express and Fastify, I'd suggest using a Guard instead.
Accepted answer didn't help me, but the "custom decorators" from nest js documentation uses createParamDecorator with ExecutionContext as second parameter, and this worked for me:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
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'm trying to create a global error handler in node js with typescript. I have created some custom classes which extends error class something like this:-
custom class
export abstract class CustomError extends Error {
abstract statusCode: number;
abstract errorMessage: string;
abstract data: unknown;
constructor() {
super();
Object.setPrototypeOf(this, CustomError.prototype);
}
abstract serializeError(): { message: string; fields?: string }[] | string;
}
Bad request error class
import { StatusCodes } from 'http-status-codes';
import { CustomError } from './custom.error';
class BadRequestException extends CustomError {
statusCode = StatusCodes.BAD_REQUEST;
errorMessage: string;
data: any;
constructor(errorMessage?: string) {
super();
// set default value
this.errorMessage = errorMessage || 'Bad Request';
Object.setPrototypeOf(this, BadRequestException.prototype);
}
serializeError() {
return this.errorMessage;
}
}
export default BadRequestException;
server.js
dotenv.config();
const app: Express = express();
app.use('/', routes);
app.use(globalErrorHandler);
export default app;
routes.ts
import express from 'express';
import UserController from '../controller/user/user-controller';
import NotFoundException from '../exception/not-found-exception';
import authorizationMiddleware from '../middleware/authorization-middleware';
const router = express.Router();
// users routes
router.post('/user/create', UserController.createUser);
router.get('/user/profile/:key', UserController.getProfile);
router.get('/user/directus', UserController.getDirectUs);
router.use('*', () => {
throw new NotFoundException();
});
export default router;
controller.ts
import { Request, Response } from 'express';
import { StatusCodes } from 'http-status-codes';
import { directus } from '../../config/directus-confgi';
import BadRequestException from '../../exception/bad-request-exception';
import { USER } from '../../interface/user-interface';
import UserService from './user.service';
class UserController {
// getting user profile
static async getDirectUs(request: Request, response: Response) {
try {
const user = await directus.items('articles').readOne(15);
response.status(StatusCodes.OK).json({ user });
} catch (error: any) {
throw new BadRequestException(error.message);
}
}
}
export default UserController;
Global Error Handler
import { StatusCodes } from 'http-status-codes';
import { Request, Response, NextFunction } from 'express';
import { CustomError } from '../exception/custom.error';
const globalErrorHandler = (err: any, req: Request, res: Response,next:NextFunction) => {
if (err instanceof CustomError) {
const error: { message: string; errors?: any } = {
message: err.errorMessage ? req.body.i18nObj.__(err.errorMessage) : '',
};
if (err.data) error.errors = err.serializeError();
return res.status(err.statusCode).json(error);
}
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
message: err.message
? err.message
: req.body.i18nObj.__('MESSAGES.UNABLE_COMPLETE_REQUEST'),
error: { message: err.message, err },
});
};
export default globalErrorHandler;
I'm getting errors in my controller catch block and bad-request error class but after that my global error handler middleware function is not able to get this error, in a result, I'm not able to send this error as a response.
I don't know what I'm doing wrong or how can call my middleware function without throwing an actual error from the controller I want to use my custom error class to formate error and status code.
In Express v4, you need to manually call next() in async controllers in order for error handling middleware to work.
Catching Errors
For errors returned from asynchronous functions invoked by route handlers and middleware, you must pass them to the next() function, where Express will catch and process them.
class UserController {
// getting user profile
static async getDirectUs(
request: Request,
response: Response,
next: NextFunction
) {
try {
const user = await directus.items("articles").readOne(15);
response.status(StatusCodes.OK).json({ user });
} catch (error: any) {
// pass the custom error to `next()`
next(new BadRequestException(error.message));
}
}
}
From Express v5 you won't have to do this
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error
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() {}
}
I want to achieve something like this using nest.js:
(something very similar with Spring framework)
#Controller('/test')
class TestController {
#Get()
get(#Principal() principal: Principal) {
}
}
After hours of reading documentation, I found that nest.js supports creating custom decorator. So I decided to implement my own #Principal decorator. The decorator is responsible for retrieving access token from http header and get principal of user from my own auth service using the token.
import { createParamDecorator } from '#nestjs/common';
export const Principal = createParamDecorator((data: string, req) => {
const bearerToken = req.header.Authorization;
// parse.. and call my authService..
// how to call my authService here?
return null;
});
But the problem is that I have no idea how to get my service instance inside a decorator handler. Is it possible? And how? Thank you in advance
It is not possible to inject a service into your custom decorator.
Instead, you can create an AuthGuard that has access to your service. The guard can then add a property to the request object, which you can then access with your custom decorator:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const bearerToken = request.header.Authorization;
const user = await this.authService.authenticate(bearerToken);
request.principal = user;
// If you want to allow the request even if auth fails, always return true
return !!user;
}
}
import { createParamDecorator } from '#nestjs/common';
export const Principal = createParamDecorator((data: string, req) => {
return req.principal;
});
and then in your controller:
#Get()
#UseGuards(AuthGuard)
get(#Principal() principal: Principal) {
// ...
}
Note that nest offers some standard modules for authentication, see the docs.
for NestJS v7
Create custom pipe
// parse-token.pipe.ts
import { ArgumentMetadata, Injectable, PipeTransform } from '#nestjs/common';
import { AuthService } from './auth.service';
#Injectable()
export class ParseTokenPipe implements PipeTransform {
// inject any dependency
constructor(private authService: AuthService) {}
async transform(value: any, metadata: ArgumentMetadata) {
console.log('additional options', metadata.data);
return this.authService.parse(value);
}
}
Use this pipe with property decorator
// decorators.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
import { ParseTokenPipe} from './parse-token.pipe';
export const GetToken = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
return ctx.switchToHttp().getRequest().header.Authorization;
});
export const Principal = (additionalOptions?: any) => GetToken(additionalOptions, ParseTokenPipe);
Use this decorator with or without additional options
#Controller('/test')
class TestController {
#Get()
get(#Principal({hello: "world"}) principal) {}
}
You can use middlewar for all controller.
auth.middleware.ts
interface AccountData {
accId: string;
iat: number;
exp: number;
}
interface RequestWithAccountId extends Request {
accId: string;
}
#Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private readonly authenticationService: AuthenticationService) {}
async use(req: RequestWithAccountId, res: Response, next: NextFunction) {
const token =
req.body.token || req.query.token || req.headers['authorization'];
if (!token) {
throw new UnauthorizedException();
}
try {
const {
accId,
}: AccountData = await this.authenticationService.verifyToken(token);
req.accId = accId;
next();
} catch (err) {
throw new UnauthorizedException();
}
}
}
Then create AccountId decorator
account-id.decorator.ts
import {
createParamDecorator,
ExecutionContext,
UnauthorizedException,
} from '#nestjs/common';
export const AccountId = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest();
const token = req.accId;
if (!token) {
throw new UnauthorizedException();
}
return token;
},
);
Then apply AccountId decorator in your controller
your.controller.ts
#Get()
async someEndpoint(
#AccountId() accountId,
) {
console.log('accountId',accontId)
}
Is it possible to make a redirect from a Nest controller without the usage of the #Response object?
For now I know that we can only do this via direct #Response object injection into the route handler.
You can write a RedirectInterceptor:
#Injectable()
export class RedirectInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
const response = context.switchToHttp().getResponse();
response.redirect('redirect-target');
return stream$;
}
}
Then use it in your controller like this:
#Get('user')
#UseInterceptors(RedirectInterceptor)
getUser() {
// will be redirected.
}
It is important not to return anything from your controller, otherwise you will get the following error: Can't set headers after they are sent.
If needed the RedirectInterceptor can be dynamic as well:
#Injectable()
export class RedirectInterceptor implements NestInterceptor {
constructor(private readonly target: string) {}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
const response = context.switchToHttp().getResponse();
response.redirect(this.target);
^^^^^^^^^^^
return stream$;
}
}
and then in the controller:
#UseInterceptors(new RedirectInterceptor('redirect-target'))
(a bit of a different implementation to another answer here...)
I created a RedirectError which can be thrown more dynamically than a decorator
import { ExceptionFilter, Catch, ArgumentsHost } from '#nestjs/common';
import { Response } from 'express';
export class RedirectError extends Error {
constructor(public readonly status: number, public readonly url: string) {
super();
}
}
#Catch(RedirectError)
export class RedirectFilter implements ExceptionFilter {
public catch(exception: RedirectError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
return response.redirect(exception.status, exception.url);
}
}
and then in main.ts set it:
app.useGlobalFilters(new RedirectFilter());
and finally to use it:
throw new RedirectError(302, '/some-target');
I've done it more complex, but I think it is good enough.
Create a class such as util/RedirectException like this:
The code like this:
import { HttpException, HttpStatus } from '#nestjs/common';
export class RedirectException extends HttpException {
constructor(message?: string | object) {
super(message, HttpStatus.CONTINUE);
}
}
Create a RedirectFilter by: nest g f RedirectFilter
Write the code like this:
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '#nestjs/common';
import { RedirectException } from './util/redirect-exception';
#Catch()
export class RedirectFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const res = host.switchToHttp().getResponse(),
req = host.switchToHttp().getRequest();
try {
if (exception instanceof RedirectException) {
Object.keys(exception.message).forEach(k => {
req.session[k] = exception.message[k];
});
req.session.save(() => {
res.redirect(exception.message.url);
});
return;
}
if (exception instanceof HttpException) {
return res.status(exception.status).json(exception.message)
}
res.status(500).json({status: 500, message: 'Internal Server error'})
} catch (e) {
return res.status(500)
.json({
status: HttpStatus.INTERNAL_SERVER_ERROR,
message: e.message
});
}
}
}
This class help you handle all the response when an exception is throw. And yes, this include the Redirect exception. Now we can throw the exception with exactly params, and it work!
Use the filter in main.ts: app.useGlobalFilters(new RedirectFilter());
And in controller, if you want to redirect to an url, just do this any time you want
Code:
throw new RedirectException({
url: 'the url you want to redirect',
field1: 'The first field you want to pass to session'
field2: 'The second field you want to pass to session'
});
Don't forget setup express-session if you want to pass data by session when redirect: https://www.npmjs.com/package/express-session.
If you don't want to use this, just replace the code inside if (exception instanceof RedirectException) {} to: res.redirect(exception.message.url);. It don't check and setup the session anymore.