Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NestJs authentication using jwt and private and public key

I'm trying to understand jwt and authentication using nestJS. I've created two separate microservices, one of them is an auth service, after successful login the client gets jwt token and with this token he can access to the other microservice.

Here is the code of the JwtStrategy and AuthModule of the auth service :

import { ExtractJwt, Strategy } from 'passport-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: 'secretKey'
    });
  }

  async validate(payload: any) {
    return payload;
  }
}
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { AuthController } from './auth.controller';
import * as fs from 'fs';

@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: 'secretKey',
      signOptions: { expiresIn: '1h' },
    }),
  ],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService],
  controllers: [AuthController],
})
export class AuthModule {}

And here is the code of the other service:

import { ExtractJwt, Strategy } from 'passport-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: 'secretKey',
    });
  }

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

I've figured out that there is no sense in using the same secret key for both services(because if I will create 10 microservices I won't use the same key for all of them) so I create a private and public key using openssl. At the AuthModule, I copy the private key instead of the 'secretKey' string and at the other service I copy the public key instead of the 'secretKey' string but I get a 401, unauthorized error. What have I missed here? why the JwtStrategy doesn't verify the public key?

like image 399
Tomer Avatar asked May 05 '20 05:05

Tomer


People also ask

How does JWT public and private key work?

With JWT, the possession and the use of the key materials are exactly the same as any other contexts where cypher operations occur. For signing: The private key is owned by the issuer and is used to compute the signature. The public key can be shared with all parties that need to verify the signature.

Does JWT use public key?

Security-wise, SWT can only be symmetrically signed by a shared secret using the HMAC algorithm. However, JWT and SAML tokens can use a public/private key pair in the form of a X.509 certificate for signing.


1 Answers

Since it's been days, I am guessing this was solved. I am just adding my two cents here for future readers.

The problem lies with the JwtModule and the JwtStrategy instantiation. They aren't configured properly. You need to pass in the algorithms you'd use for signing and verifying the tokens, along with the keys. To verify whether the tokens are actually getting generated with the RS256 algo, check the header in the token at https://jwt.io/. It would probably show HS256, and since your code didn't use the correct algorithm to sign the token. And it fails while token gets verified using the public key.

To generate signed tokens properly with the RSA key pair:

  • You need to add algorithm in the signOptions as RS256 and pass in the the public and private keys in the JwtModule configuration.
  • Then within your service, you'd generate the token with the PRIVATE_KEY when you sign.
  • JwtStrategy is used as a Guard. All it does is verify the JWT based on configuration. It expects either the symmetric key "secret" or the "public part" of the asymmetric key to verify. We have to use the PUBLIC_KEY. You also have to specify the algorithms to check for verifying here. We have to use RS256 here as well, since we used that to generate the token.

Auth Module

@Module({
  imports: [
    ConfigModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => {
        const options: JwtModuleOptions = {
          privateKey: configService.get('JWT_PRIVATE_KEY'),
          publicKey: configService.get('JWT_PUB LIC_KEY'),
          signOptions: {
            expiresIn: '3h',
            issuer: '<Your Auth Service here>',
            algorithm: 'RS256',
          },
        };
        return options;
      },
      inject: [ConfigService],
    }),
  ],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService],
  controllers: [AuthController],
})
export class AuthModule {}

Auth Service

@Injectable()
export class AuthService {
  constructor(
    private jwtService: JwtService,
  ) {}

  async generateToken(
    user: User,
    signOptions: jwt.SignOptions = {},
  ): Promise<AuthJwtToken> {
    const payload = { sub: user.id, email: user.email, scopes: user.roles };
    return {
      accessToken: this.jwtService.sign(payload, signOptions),
    };
  }
}

JwtStrategy

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get('JWT_PUBLIC_KEY'),
      algorithms: ['RS256'],
    });
  }

  async validate(payload: any) {
    const { sub: userId, email, scopes: roles } = payload;
    return {
      id: userId,
      email,
      roles,
    };
  }
}

In your other micro-services, You can use the same JwtStrategy we used in the Auth module.

Since you're creating a distributed app, you need to share the PUBLIC_KEY with the other micro-services by manually adding the key or by exposing it using some API endpoint. Either way, you have to use the PUBLIC_KEY for the other services to verify. You must not share or expose the PRIVATE_KEY.

NOTE: The following code assumes a ConfigService which would provide the RSA key pair form env. It's strongly suggested not to check in the keys in the code.

like image 58
Avik Sarkar Avatar answered Sep 19 '22 14:09

Avik Sarkar