18

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.

Basically, I need a way to propagate req.user on every(or at least on some of not AuthGuard'ed request), it is really straight forward to do in express by applying passport middleware, but I'm 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 };
}
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
ZVER3D
  • 262
  • 1
  • 3
  • 10
  • I think one can directly use,`me(@Request() req) { return req.user }` which will give us all the details of the current user. – rickster Aug 08 '21 at 17:44

3 Answers3

31

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)
  }
vpdiongzon
  • 629
  • 5
  • 6
4
"Basically, 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."

To achieve this we write an interceptor because we need to use the UsersService. UserService is part of the dependency injection system. We cannot just import the user service and create a new instance of it ourselves. The service makes use of the users repository and that users repository is setup only through dependency injection.

The thing is we cannot make use of dependency injection with a parameter decorator. This decorator cannot reach into the system in any way and try to get access to some instance of anything inside there. This is how we write the interceptor. I make comments on the code:

//  this interceptor will be used by the custom param decoratro to fetch the current User
import {NestInterceptor,ExecutionContext,CallHandler,Injectable} from '@nestjs/common';
import { UsersService } from '../users.service';

@Injectable()
// "implements" guide us how to put together an interceptor
export class CurrentUserInterceptor implements NestInterceptor {
  constructor(private userService: UsersService) {}
  // handler refers to the route handler
  async intercept(context: ExecutionContext, handler: CallHandler) {
    const request = context.switchToHttp().getRequest();
    const { userId } = request.session || {};
    if (userId) {
      const user = await this.userService.findOne(userId);
      // we need to pass this down to the decorator. SO we assign the user to request because req can be retrieved inside the decorator
      // ------THIS IS WHAT YOU WANTED--------
      request.currentUser = user;
    }
    // run the actual route handler
    return handler.handle();
  }
}

Now you need to register this to the module:

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService, AuthService, CurrentUserInterceptor],
 })

Inside controller:

@Controller('auth')
@UseInterceptors(CurrentUserInterceptor)
export class UsersController {
  constructor("inject services) {}

  @Get('/me')
  me(@CurrentUser() user: User) {
    return user;
  }
}

In any route handler you use CurrentUser param decorator, you will have access to "user".

You actually do not need to write a custom param decorator

you could just use the interceptor, its implementation would be different:

@Get('/me')
me(@CurrentUserInterceptor() request: Request) {
  // You have access to request.currentUser
  return  request.currentUser
}

Set interceptor globally

The current setup for the interceptor is tedious. We are applying the interceptor to one controller at a time. (Thats called controlled scope) Instead you could globally make this interceptor available:

users Module:

import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  // this createes repository
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [
    UsersService,
    AuthService,
    {
      provide: APP_INTERCEPTOR,
      useClass: CurrentUserInterceptor,
    },
  ],
})

This approach has one downside. Not every controller cares about what the current user is. In those controllers, you still have to make request to the database to fetch the current User.

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
-2

the parsed userinfo stored in request.user

import {Req}  from '@nestjs/common'
import { Request } from 'express'

@Post()
create(@Req() request: Request) {
    console.log('user', request.user)
}
YAN7
  • 73
  • 3