Optional Authentication in nestjs

Viewed 7396

I want to know if there is a decorator that makes the req.user object available in a controller method, if the user is logged in (Authaurization header sent), if not then just let the req.user be null.

The AuthGuard decorator will return 401 if the user is not logged in, so it's not suitable for my case.

2 Answers

There is no built-in decorator but you can easily create one yourself. See the example from the docs:

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

export const User = createParamDecorator((data, req) => {
  return req.user;
});

Since the built-in AuthGuard throws an exception, you can create your own version and overwrite the request handler:

@Injectable()
export class MyAuthGuard extends AuthGuard('jwt') {

  handleRequest(err, user, info) {
    // no error is thrown if no user is found
    // You can use info for logging (e.g. token is expired etc.)
    // e.g.: if (info instanceof TokenExpiredError) ...
    return user;
  }

}

Make sure that you are not throwing errors in your JwtStrategy:

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'secretKey',
    });
  }

  async validate(payload) {
    const user = await this.authService.validateUser(payload);
    // in the docs an error is thrown if no user is found
    return user;
  }
}

Then you can use it in your Controller like this:

@Get()
@UseGuards(MyAuthGuard)
getUser(@User() user) {
  return {user};
}

Another way of doing this is by creating an anonymous passport strategy:

// In anonymous.strategy.ts
@Injectable()
export class AnonymousStrategy extends PassportStrategy(Strategy, 'anonymous') {
  constructor() {
    super()
  }

  authenticate() {
    return this.success({})
  }
}

Then, chaining this strategy in the controller:

// In create-post.controller.ts
@Controller()
export class CreatePostController {
  @UseGuards(AuthGuard(['jwt', 'anonymous'])) // first success wins
  @Post('/posts')
  async createPost(@Req() req: Request, @Body() dto: CreatePostDto) {
    const user = req.user as ExpressUser

    if (user.email) {
      // Do something if user is authenticated
    } else {
      // Do something if user is not authenticated
    }
    ...
  }
}
Related