10

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],
})
Sebastian Richner
  • 782
  • 1
  • 13
  • 21
Arnab De
  • 402
  • 4
  • 12

4 Answers4

11

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

Jay McDoniel
  • 57,339
  • 7
  • 135
  • 147
  • 1
    Any additional configurations to take care of? With me, this still uses the initial JwtService – Tobias Famos Jul 13 '21 at 11:57
  • 2
    You do this module for each `JwtService` you want to use, and then just import this module where you need the new service. Then use the specific name instead of `JwtService` – Jay McDoniel Jul 13 '21 at 15:03
  • @JayMcDoniel would you be able to show an example of using instances of this in the same module? I just cannot come right here. – Brendan Aug 20 '21 at 17:58
  • 1
    You can't do it in the same module. That's why you have to do these separate modules with `useExisting` and custom provider names – Jay McDoniel Aug 20 '21 at 18:07
  • @JayMcDoniel I have tried to upgrade to nest v8, however this solution is now breaking? Have you got any idea how I can go about fixing it? – Brendan Jan 24 '22 at 06:21
  • 1
    @JayMcDoniel The error I am getting is ```[Nest] 8275 - 24/01/2022, 08:19:29 [ExceptionsHandler] this.jwtRefreshService.sign is not a function +1484ms TypeError: this.jwtRefreshService.sign is not a function``` – Brendan Jan 24 '22 at 06:21
  • That would seem like an `undefined` is being injected for some reason. IT works fine on Nest 8 for me – Jay McDoniel Jan 24 '22 at 22:39
  • @JayMcDoniel I am implementing something similar and I have one question. How each guard would know which JWT module inject and with that its own Pub/Priv key? Because until now I just use PassportLocal and JWTModule but now I would like to use with accessToken and refreshToken the JWTModule. Another option could be to add in the constructor the options of the token? – DustInTheSilence Mar 03 '22 at 15:13
  • @JayMcDoniel solved the issued that I have through the NestJS team discord! – DustInTheSilence Mar 04 '22 at 11:11
  • I believe a simpler solution would be to name your Passport strategies like so `export class AccessTokenStrategy extends PassportStrategy(Strategy, 'access') { ... }` and then also update your guards like so `export class AccessTokenGuard extends AuthGuard('access') { ... }` The `JwtModule.register` that is imported will then hold the default settings (e.g. for the access token). Whenever you need to call the sign function to create the refresh token, you can override these settings by passing them as second argument. – user3199765 Sep 30 '22 at 21:49
2

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

  • Additionally, you have to update your guards to use the name you provided to PassportStrategy, like so `export class AccessTokenGuard extends AuthGuard('access')` – user3199765 Sep 30 '22 at 21:45
1

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' },
      ),
    };
  }
}
Community
  • 1
  • 1
Gabriel Brito
  • 1,003
  • 2
  • 16
  • 26
0

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;
  }
}
Strak
  • 99
  • 7