How to implement multiple JWT strategies using multiple secrets in Nest.JS

8.2k views Asked by At

I like to implement more than one named passport-JWT strategy, each with its own secret. Is there any way it can be implemented? From what I can understand from the documentation, only one secret can be registered during module initialization:

@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: jwtConstants.secret,
      signOptions: { expiresIn: '60s' },
    }),
  ],
  providers: [AuthService, LocalStrategy],
  exports: [AuthService, JwtModule],
})
4

There are 4 answers

10
Jay McDoniel On

To allow for the registration of multiple variants of the same service, you're going to need to use a custom provider and wrapper module around the JwtModule. It would probably look something like this:

@Module({
  imports: [JwtModule.register({
    secret: secret1,
    signOptions: { expiresIn: '60s' },
  })],
  providers: [{
    provide: 'JwtSecret1Service',
    useExisting: JwtService,
  }],
  exports: ['JwtSecret1Service'],
})
export class JwtSecret1Module {}

Now you can use @Inject('JwtSecret1Service') to use this specific configuration so long as JwtSecret1Module has been added to the imports of the consuming module. You can do this with as many variants of the JwtService as you want, and each one will hold it's own configuration

1
Shamkhal Zeynalzade On

I did the same thing a few days ago. I created refresh token and access token. here is the app module:

imports: [..., JwtModule.register({})]

I registered JwtModule just like this. if you want to create access token: access token strategy:

export class AccessStrategy extends PassportStrategy(Strategy, 'access') {
constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: JwtConstants.access_token_secret,
    });
  }

  validate(payload: any) {
    return payload;
  }
}

and if you want to create access token:

accessToken(userId: number, username: string) {
const token = this.jwtService.signAsync(
  {
    sub: userId,
    username: username,
  },
  {
    secret: JwtConstants.access_token_secret,
    expiresIn: 60,
  },
);
if (token) {
  return token;
}
return null;
}

you can do same thing for refresh token or your another tokens

0
Gabriel Brito On

Basically when you create your strategy you can add a second argument for the strategy name. Then you can specify which JWT strategy you require on your AuthGuard.

// auth/strategy/jwt-access.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';

@Injectable()
export class AccessTokenStrategy extends PassportStrategy(Strategy, 'jwt-access-token') {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: 'access-token-secret',
    });
  }
  async validate(payload: any) {
    // your validate implementation here
    return {};
  }
}

// auth/guard/jwt-access-auth.guard.ts
import { ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class AccessTokenAuthGuard extends AuthGuard('jwt-access-token') {
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req;
  }
}

You can keep your auth.module.ts as is, and in your service you can use the options param of jwtService.sign as in the code below

@Injectable()
export class AuthService {
  ...
  login(user: User) {
    return {
      access_token: this.jwtService.sign({ /* object to sign */ }),
      refresh_token: this.jwtService.sign(
        { /* object to sign */ }
        { secret: 'refresh-token-secret', expiresIn: '14d' },
      ),
    };
  }
}
0
Strak On

I recently created a package for managing this, extending passport-jwt to allow an array of passport-jwt configurations to be passed to the constructor. When a request arrives, each config is checked in parallel using the standard passport-jwt code to see if the JWT should be authorised.

Here is the package: https://www.npmjs.com/package/@mestrak/passport-multi-jwt.

In NestJS you would do something like this in jwt.strategy.ts (or whatever your strategy setup file is called.

import { ExtractJwt, Strategy } from '@mestrak/passport-multi-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: 'a_secret_key',
    },
    {
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: 'another_secret_key',
    }]);
  }

  async validate(payload: any) {
    return payload;
  }
}