3
votes

I use nestjs with passport with jwt strategy. And I want to get a current user on some of my requests. Currently i have a decorator that looks like this:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const user = ctx.switchToHttp().getRequest().user;

    if (!user) {
      return null;
    }

    return data ? user[data] : user; // extract a specific property only if specified or get a user object
  },
);

It works as intended when i use it on a route with an AuthGuard:

@Get('test')
  @UseGuards(AuthGuard())
  testRoute(@CurrentUser() user: User) {
    console.log('Current User: ', user);
    return { user };
  }

But how do i make it work (get current user) on non-guarded routes? I need users to be able to post their comments regardless of if they are authorized or not, however, when they are logged in, i need to get their name.

Basicaly, I need a way to propagate req.user on every(or at least on some of not AuthGuard'ed request), it is realy straight forward to do in express by applying passport middleware, but im not sure how to do it with @nestjs/passport.

[EDIT] Thanks to vpdiongzon for pointing me in the right direction, I chose to make a guard based on his answer, that just populates req.user with either user or null:

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class ApplyUser extends AuthGuard('jwt') {
  handleRequest(err: any, user: any) {
    if (user) return user;
    return null;
  }
}

And now I could just use it on any unprotected route that needs to get the current user

@Get('me')
@UseGuards(ApplyUser)
me(@CurrentUser() user: User) {
  return { user };
}
1

1 Answers

6
votes

You need to apply your AuthGuard to every route regardless but if you have a route that don't require authentication just add a custom decorator, example:

the Auth Guard

export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private readonly reflector: Reflector) {
    super();
  }

  handleRequest(err, user, info, context) {
    const request = context.switchToHttp().getRequest();       

    const allowAny = this.reflector.get<string[]>('allow-any', context.getHandler());
    if (user) return user;
    if (allowAny) return true;
    throw new UnauthorizedException();
  }
}

Apply globally the AuthGuard in app.module.js

import { APP_GUARD, Reflector } from '@nestjs/core';
import { AppController } from './app.controller';
import { JwtAuthGuard } from './app.guard';



@Module({
  imports: ],
  controllers: [AppController],
  providers: [
    {
      provide: APP_GUARD,
      useFactory: ref => new JwtAuthGuard(ref),
      inject: [Reflector],
    },
    AppService,
  ],
})
export class AppModule {
}

The custom decorator to allow a route without authentication

import { SetMetadata } from '@nestjs/common';

export const AllowAny = () => SetMetadata('allow-any', true);

Apply AllowAny in a route, if AllowAny decorator is not attached in a controller route it will required a user.

  @Post('testPost')
  @AllowAny()
  async testPost(@Req() request) {
    console.log(request.user)
  }