nestjs/jwt

Refresh tokens

Epenance opened this issue ยท 11 comments

I'm submitting a...


[ ] Regression 
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

When the access token expires we are forced to show the user a login form again so they can manually authenticate again, rather than automaticly renewing the token using a refresh token.

Expected behavior

When the token expires we should get a 401, whereafter we try to refresh the access token using our refresh token, if succeeded we should get a new set of keys, else we should invalidate both tokens forcing the user to login again.

What is the motivation / use case for changing the behavior?

Using refresh tokens has been pretty standardized within the use of JWT's, we are making a unnatural flow without them.

https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/

Refresh tokens mechanism can be easily implemented using this package. Nothing more is needed

Refresh tokens mechanism can be easily implemented using this package. Nothing more is needed

@kamilmysliwiec The ingenious answer. Can you show this easy implementation?

@Photon79 You may generate two tokens one as access_token second as refresh_token with different expiresIn time.

mmv08 commented

@shamahan Yes, but where do you refresh one?

For me one of the possibilities seems to be implementing a custom AuthGuard which would throw a custom exception which then would be caught by exception filter

@shamahan Yes, but where do you refresh one?

My solution is: /auth/refresh route which accept refresh token from headers then validate by secret and check token in db. If token valid and stored in db I would recreate tokens and update refresh token in db.

Took me half a day to figure this out, so I hope this helps others.

E.g For Token Creation/Signing

  • Module
@Module({
  imports: [JwtModule.register({})], // Must register with empty options object...
})
export class SomeModule {}
  • Signing/Creation
constructor(private jwtService: JwtService){}
...
...
const payload = { userdetails: userdetails };
const accessToken = this.jwtService.sign(payload, {
  secret: accessConfig.secret, // unique access secret from environment vars
  expiresIn: accessConfig.signOptions.expiresIn, // unique access expiration from environment vars
});

const refreshToken = this.jwtService.sign(payload, {
  secret: refreshConfig.secret, // unique refresh secret from environment vars
  expiresIn: refreshConfig.signOptions.expiresIn, // unique refresh expiration from environment vars
});
...
// Store refresh **token-hash** in DB
...
// return tokens to client
return {
  accessToken: accessToken,
  refreshToken: refreshToken,
};

Eg. For Token AuthGuard Strategy

  • AuthGuard Strategy
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(
  Strategy,
  'jwt-refresh-strategy',
) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtRefreshConfig.secret, // gets secret from environment variables
    });
  }

  async validate(payload: any) {
    // DB checks for refresh token: valid, revoked, etc...
    ...
    ...
    return { username: payload.username };
  }
}

My suggested changes to this package:

  • Handle registration if JwtModule is imported but not registered with empty object.
  • Update docs to make clear that JwtModule can simply be imported, and JwtService can be passed options.

Eg. What this package should do:

@Module({
  imports: [JwtModule], // Import like any other module
})
export class SomeModule {}

...
...
// Package should have some conditional check
// Sudo code example
if(!JwtModuleRegistered) {
  JwtModule.register({});
}

You can follow the same instructions with this package as it's using the jsonwebtoken under the hood.

You can follow the same instructions with this package as it's using the jsonwebtoken under the hood.

Well.... I just took a look at her again, and you obviously are correct, but man the docs should really point this out. I'll update my previous comment to reflect the correct way using this package.

Thanks!

@brock-wood Contributions to the docs are more than welcome! If you are interested, feel free to create a PR with any improvements you think might be useful ๐Ÿ’ช

@brock-wood Many thanks! โค๏ธ

Just for the reference, here is my VERY basic implementation: modernweb-pl/vue-nest-monorepo#32