1

I'm in the process of implementing refresh tokens and I use passportjs. What I don't completely understand is where and how I should check access tokens for validity and in case if an invalid token arrives throw TokenExpiredException.

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

    public async validate(payloadDto: PayloadDto): Promise<PayloadDto> {
        const validUser = await this.authService.validateUser(payloadDto);
        return { id: validUser.id, phone: validUser.phone };
    }
}

The validateUser method currently looks like this:

    public async validateUser(payload: PayloadDto): Promise<UserEntity> {
        const retrievedUser: UserEntity = await this.userService.retrieveOne(payload.phone);
        if (retrievedUser) {
            return retrievedUser;
        } else {
            throw new HttpException('Invalid User', HttpStatus.UNAUTHORIZED);
        }
    }

I'm wondering if it's secure to check it like this:

@Injectable()
export class RefreshAuthGuard extends AuthGuard('jwt') {
    public handleRequest(err: any, user: any, info: Error): any {
        if (info) {
            if (info.name === 'TokenExpiredError') {
                throw new HttpException('TokenExpired', HttpStatus.UNAUTHORIZED);
            } else {
                throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
            }
        }
    }
}
Albert
  • 2,146
  • 10
  • 32
  • 54

1 Answers1

2

I would suggest changing your auth flow as follows (also see thread and thread):

  1. The client tries to call the protected route /secret with an expired auth token
  2. The server throws a TokenExpiredError to the client
  3. The client now requests a new access token at the auth server with its valid refresh token
  4. The auth server checks the refresh token and issues a new access token to the client
  5. The client retries /secret with its new access token

The whole purpose of a refresh token is that it never gets shared with the resource server and is not send around with every request; this increases security. If the resource server makes the refresh request itself, you defeat this purpose. If the resource server and the auth server are the same, you still benefit from not sending the long-lived (➡ higher risk) tokens around so much, i.e., less chance for them to be compromised through a person-in-the-middle attack.

Kim Kern
  • 54,283
  • 17
  • 197
  • 195
  • Thank you a lot! Just for curiosity's sake, are there any alternatives to refresh tokens utilizing the architecture this solution implies? – Albert May 16 '20 at 15:40
  • I mean are there any ways to do without that auth server, that must be a separate microservice, as far as I understand. – Albert May 16 '20 at 15:50
  • All of this strongly depends on your requirements, it's hard to give a general answer. The auth server and resource server can be the same server, you still benefit from not sending long-lived tokens around all the time. However, you might not need refresh tokens. What did you want the refresh tokens for in the first place? Do you want to be able to revoke long-lived tokens while maintaining an auth flow without db access (performance)? Because you can also have session management without additional refresh tokens. Really depends on what you want to do. ‍♂️ – Kim Kern May 16 '20 at 17:58