How to get JWT token from headers in controller
Asked Answered
E

5

16

Please, help me to find how to optimize my code. I need to limit the data for logged user. To do that, I need to get UUID from JWT token from Request. But I don't like my approach because I have duplicates of code:

const jwt = request.headers.authorization.replace('Bearer ', '');
const json = this.jwtService.decode(jwt, { json: true }) as { uuid: string };

Any one know how I can optimize that?

Here is my controller's code.

import { Controller, Get, Put, Body, Param, UseGuards, Req } from '@nestjs/common';
import { SettingService } from '../services';
import { AuthGuard } from '@nestjs/passport';
import { ResultInterface } from '../interfaces';
import { Request } from 'express';
import { JwtService } from '@nestjs/jwt';

@Controller('settings')
export class SettingController {
  /**
   * @param service
   * @param jwtService
   */
  constructor(private readonly service: SettingService,
              private readonly jwtService: JwtService) {
  }

  @UseGuards(AuthGuard('jwt'))
  @Get()
  async findAll(@Req() request: Request): Promise<ResultInterface> {
    const jwt = request.headers.authorization.replace('Bearer ', '');
    const json = this.jwtService.decode(jwt, { json: true }) as { uuid: string };
    const data = await this.service.findAll(json.uuid);
    return { rows: data };
  }

  @UseGuards(AuthGuard('jwt'))
  @Get(':id')
  async findOne(@Param('id') id: number, @Req() request: Request): Promise<ResultInterface> {
    const jwt = request.headers.authorization.replace('Bearer ', '');
    const json = this.jwtService.decode(jwt, { json: true }) as { uuid: string };
    const data = await this.service.findOneById(id, json.uuid);
    return { row: data };
  }

  @UseGuards(AuthGuard('jwt'))
  @Put()
  update(@Body() data: any, @Req() request: Request): Promise<any> {
    const jwt = request.headers.authorization.replace('Bearer ', '');
    const json = this.jwtService.decode(jwt, { json: true }) as { uuid: string };
    return this.service.update(data, json.uuid);
  }
}
Emmer answered 7/9, 2019 at 12:25 Comment(0)
S
19

In order to make your code more readable and transparent, you can create a @AuthUser() decorator and reuse it across all of your controllers. When you are using AuthGuard('jwt') you already are decoding the token and if you are using jwt.decode function again you double decode the token.

I attached below and example of user decorator and order controller function that returns all my orders.

user.decorator.ts

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

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

order.controller.ts

 @ApiOperation({ title: 'Get my orders' })
 @Get('/me')
 @UseGuards(AuthGuard('jwt'))
 async findMyOrders(@AuthUser() user: any): Promise<Order[]> {
   return this.orderService.findbyUserId(user._id);
 }
Substance answered 10/9, 2019 at 9:31 Comment(4)
Much better than mine!Ceaseless
Cool! Good idea. Thank youEmmer
The method to create a decorator changed, here is the doc about it docs.nestjs.com/custom-decorators.Aftertaste
In my case the user object isn't located directly in the req param. I had to access it as shown in the custom decorator Nest docs. Adapting the answer it would be req.switchToHttp().getRequest().user. req is of ExecutionContext type, imported from @nestjs/common.Bigwig
C
4

You could create a JWTUtil that does that for you... Maybe something like this?

@Injectable()
export class JWTUtil {
    constructor(private readonly jwtService: JWTService) {}

    decode(auth: string): {uuid: string}{
        const jwt = auth.replace('Bearer ', '');
        return this.jwtService.decode(jwt, { json: true }) as { uuid: string };
    }
}

And then use it like this:

@Controller('settings')
export class SettingController {
  constructor(
      private readonly jwtUtil: JWTUtil,
      private readonly service: SettingService,
      ) {}

  @Get()
  @UseGuards(AuthGuard('jwt'))
  async findAll(@Headers('Authorization') auth: string): Promise<ResultInterface> {
    const json = await this.jwtUtil.decode(auth);
    const data = await this.service.findAll(json.uuid);

    //....
  }
}

Also note that you can directly access the Authorization header from the controller. Instead of passing through the Request object.

Ceaseless answered 7/9, 2019 at 20:9 Comment(1)
As I said in my previous response in the answer you gave you are double decoding the token, first when using AuthGuard('jwt) and second in controller function jwtUtil.decode()Substance
H
1

You could use AuthGuard to handle the request and pass the user information to every authenticated request.

const USER_ID_HEADER_NAME = 'x-user-id';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return super.canActivate(context);
  }

  handleRequest<TUser = any>(
    err: any,
    user: any,
    info: any,
    context: ExecutionContext,
  ): TUser {
    if (err || !user) {
      throw err || new UnauthorizedException();
    }

    const request = context.switchToHttp().getRequest();
    request.headers[USER_ID_HEADER_NAME] = user.id;
    return user;
  }
}

And Controller can assess USER_ID_HEADER_NAME to get the user information.

Hildehildebrand answered 26/7, 2022 at 7:6 Comment(0)
M
0

You are using request.headers.authorization... use this as easy;

async findAll(@Req() request: Request): Promise<ResultInterface> {
  const authToken = request.headers['authorization'].split(' ')[1];
}
Metabolic answered 11/10, 2023 at 9:59 Comment(0)
S
0

For anyone using passport-jwt, if you have a jwt.strategy.ts that looks like this:

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  validate(payload: any) {
    // What this function returns is what req.user will be set to everywhere
    return { user_id: payload.sub };
  }
}

what you let the validate() method return is what req.user will end up being. So if you apply its JwtAuthGuard globally as described in nestjs' Passport tutorial, you can get the user_id decoded from the JWT like so. This is my user.controller.ts, where .getUsername() is a database lookup:

import { Controller, Get, Request } from '@nestjs/common';
import { UsersService } from 'src/users/users.service';

@Controller('api/user')
export class UserController {
  constructor(private readonly usersService: UsersService) {}

  @Get('username')
  username(@Request() req) {
    return this.usersService.getUsername(req.user.user_id);
  }
}
Sector answered 5/4, 2024 at 11:58 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.