13

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.

Agnibha
  • 613
  • 1
  • 11
  • 20

4 Answers4

19

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.

Jay McDoniel
  • 57,339
  • 7
  • 135
  • 147
  • Hi Jay, Thanks for your answer. This works but is a workaround and `response.locals` is kinda same and this thing is not elegant and definitely not readable. – Agnibha Nov 05 '19 at 15:49
  • What data are you trying to pass from the guard to the controller? – Jay McDoniel Nov 05 '19 at 15:55
  • 1
    The way Passport does it (from a middleware, not a guard, but same idea) is to attach it to `req.user`. Other than that, or possibly trying to get some clever/tricky reflect-metadata setup going on, there isn't another way. The guard is there to return true or false (or an async variant of it) so that the system knows whether or not to continue the request or return an error code. – Jay McDoniel Nov 05 '19 at 16:21
  • > And in your controller you could then pull `req.myData`... And how exactly would that look in the controller? – Johan Faerch Nov 03 '20 at 14:52
  • Either make a custom decorator, or in the handler have `@Req() req` and then `req.myData` – Jay McDoniel Nov 03 '20 at 15:38
  • For me, passing an argument to the guard is quite useful to validate a joi schema without using the class way. I'm used to that with type-graphql, but seems like it's not a thing in nestjs. – Omar Dulaimi Nov 07 '21 at 20:00
  • @OmarDulaimi I'm not sure what you mean. You can use a mixin to pass an object to the guard class without needing to use reflection or something like `class-validator`. This way you could also still use DI for the guard and it's dependencies – Jay McDoniel Nov 07 '21 at 22:27
  • 1
    Additionally, you can create a custom decorator to ease the process of accessing the req.myData object. – Ambrus Tóth Dec 30 '21 at 21:26
16

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

Thanh Le Hai Hoang
  • 1,313
  • 7
  • 14
  • So basically it just to transport the `userId` via the `body` itself. So that means there most be @Body on event function that would need this form of Authoriztion. – Vixson Nov 19 '21 at 13:29
  • @Vixson the `userId` is not transported via the `body`. Instead, it is injected using a decorator. – thewebjackal Aug 09 '22 at 17:13
2

You can use a combination of Decorator and Pipe to safely retrieve and transform data to what you need, for instance getting the user authenticated using a JSON Web Token.

Here is what it looks like in use in a controller.

// profile.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AuthenticationToken } from 'src/authentication/authentication.decorator';
import { UserEntity } from 'src/users/users.entity';
import { UserFromTokenPipe } from 'src/users/users.pipe';

@Controller('profile')
export class ProfileController {
  @Get()
  public getProfile(@AuthenticationToken(UserFromTokenPipe) user: UserEntity) {
    const { email, firstname, lastname } = user;

    return {
      email,
      firstname,
      lastname
    }
  }
}

Here is what it looks like in the decorator.

// authentication.decorator.ts
import { createParamDecorator, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Request } from 'express';

export const AuthenticationToken = createParamDecorator((_data: unknown, context: ExecutionContext) => {
  const request = context.switchToHttp().getRequest<Request>();

  const authorizationToken = request.headers.authorization;

  if (!authorizationToken) {
    throw new UnauthorizedException("Missing authorization token");
  }

  const [bearer, token] = authorizationToken.split(' ');

  if (bearer !== 'Bearer') {
    throw new UnauthorizedException("Invalid authorization token type");
  }

  return token;
});

And here is what it looks like in the pipe.

// users.pipe.ts
import { ArgumentMetadata, Injectable, PipeTransform, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserRole } from './users.enum';
import { UsersService } from './users.service';

@Injectable()
export class UserFromTokenPipe implements PipeTransform {
  public constructor(
    private readonly jsonWebTokenService: JwtService,
    private readonly usersService: UsersService
  ) { }

  public async transform(token: string, _metadata: ArgumentMetadata) {
    try {
      const payload = this.jsonWebTokenService.verify(token);

      const user = await this.usersService.findOneById(payload.id);

      if (!user) {
        throw new UnauthorizedException("Invalid user");
      }

      return user;
    } catch (error) {
      if (error instanceof UnauthorizedException) {
        throw error;
      }

      throw new UnauthorizedException("Token");
    }
  }
}

By creating a parameter decorator and a pipe, you can virtually turn anything from the request into a concrete object, the most obvious is to turn a JSON Web Token into an authenticated user but I'll let you find other use cases as well.

Amin NAIRI
  • 2,292
  • 21
  • 20
-5

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