#How to create two JWT strategies

34 messages · Page 1 of 1 (latest)

tender lion
#

So I have two strategies in JWT, the first is for the access token and the other is for the refresh token, but the two are actually clashing.

I've made it like this
JWT Strategy

// Import
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
  constructor(
    private readonly configService: ConfigService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: configService.get('JWT_SECRET'),
    });
  }

  async validate(jwtPayload: JwtPayload): Promise<User> {
    // Code
  }
}

Refresh Token JWT Strategy

// Import
@Injectable()
export class JwtRefreshTokenStrategy extends PassportStrategy(
  Strategy,
  'jwt-refresh',
) {
  constructor(
    private readonly configService: ConfigService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: configService.get('JWT_REFRESH_SECRET'),
      passReqToCallback: true,
    });
  }

  async validate(request: Request, jwtPayload: JwtPayload): Promise<User> {
    // Code
  }
}

I've also made each guard like this
JWT Strategy

import { ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class AccessTokenGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride('isPublic', [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) return true;

    return super.canActivate(context);
  }
}

Refresh Token JWT Strategy

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

@Injectable()
export class RefreshTokenGuard extends AuthGuard('jwt-refresh') {
  constructor() {
    super();
  }
}

glossy jay
#

What's the error you're running into?

tender lion
#

not error, but it returns Unauthorized

glossy jay
#

Add this to your guard(s):

handleRequest(...args: Parameters<ReturnType<typeof AuthGuard>['prototype']['handleRequest']> {
  console.log(args);
  return super.handleRequest(...args);
}
glossy jay
#

To whichever one is failing

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

@Injectable()
export class RefreshTokenGuard extends AuthGuard('jwt-refresh') {
  constructor() {
    super();
  }

  handleRequest(
    ...args: Parameters<
      ReturnType<typeof AuthGuard>['prototype']['handleRequest']
    >
  ) {
    console.log(args);
    return super.handleRequest(args);
  }
}

super.handleRequest(args) -> error

glossy jay
#

You might need to spread the args to the super call

#

Actually, yeah, that's what it is

tender lion
#

I know

#

Error: No auth token
at JwtRefreshTokenStrategy.JwtStrategy.authenticate (E:\projects\imkey-backend\node_modules\passport-jwt\lib\strategy.js:96:26)

#

but bearer found

smoky galleon
#

@tender lion - So, a couple things here.

You are expecting to load the two tokens in the same Authorization Bearer header. I don't believe that will work in general and for sure it's not standard.

And overall, your refresh token should never be in the header. Doing that means your token is available in your client application's memory and that badly exposes the token to XSS attacks. Your refresh token, at a minimum, must be in an http-only cookie. This keeps it away from access by JavaScript in the client app, so it completely avoids any XSS issues.

And with it being in a cookie, you simply need an end-point that reads out the cookie and checks for validity and sends the proper responses back, either a new refresh token (as a new cookie) and a new access token, or whatever it is your client app needs to log out the user.

#

The only guard you need is for the access token.

tender lion
#

i know, thank you sir

tender lion
smoky galleon
#

Absolutely. You need the refresh token process, because the access token has expired or for whatever reason is invalid. So, in the refresh process, you must send back a new and fresh access token.

#

It's where the name comes from. 🙂 Refresh is for refreshing the access token.

tender lion
#

so sending the refresh token on the route doesn't need to be secured, because this is a REST API I think it's better secured

smoky galleon
#

How can you secure it?

#

the access token is invalid.

tender lion
#

oke i now, but user send userId and cookie containing the refresh token?

smoky galleon
#

You don't need to guard it, because the whole idea of the endpoint is checking the refresh token cookie. Why check it twice?

#

The user should only send the refresh token. The user's id should be inside it.

tender lion
#

The delivery has been included in the cookie?

smoky galleon
#

The delivery of the user id? It's in the token. Well, it's in the token, if you store it in the token. 🙂

#

What you store in the token is up to you.

smoky galleon
#

What is a delivery token?

tender lion
#

you said earlier that the token is better sent through a special cookie not in the header

#

access token

smoky galleon
#

The refresh token should be sent via an http-only cookie.

#

That cookie is, of course, set on the server.