Arnab De
Arnab De

Reputation: 462

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

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],
})

Upvotes: 12

Views: 10828

Answers (5)

Anwar kedir
Anwar kedir

Reputation: 1

Add a guard as much you need like this AuthGuard(['jwt', 'jwt_2']), then strategies like this:

export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {} 
export class JwtStrategy2 extends PassportStrategy(Strategy, 'jwt2') {}

Upvotes: 0

Gabriel Brito
Gabriel Brito

Reputation: 1198

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' },
      ),
    };
  }
}

Upvotes: 3

Shamkhal Zeynalzade
Shamkhal Zeynalzade

Reputation: 57

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

Upvotes: 2

Strak
Strak

Reputation: 149

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;
  }
}

Upvotes: 1

Jay McDoniel
Jay McDoniel

Reputation: 70061

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

Upvotes: 12

Related Questions