amazon-cognitonestjsbearer-tokenpassport-jwtnestjs-passport

nestjs jwt token(COGNITO generated) validation failed


We have app that uses COGNITO Userpools to have SSO enabled with oauth2; and the upon successfull login COGNITO generates token and returns; aPI would use that token for subsequent calls; Our APIs are protected with NESTJS AuthGuards;

The problem is the when we test API by passing any junk in "authorization" header like "bearer xyz" it works and woudlnt throw any error.

oauth2 code where it talks to COGNITO for token generatation;

 enter code here

    export class OAuth2Strategy extends PassportStrategy(Strategy, 'oauth2') { 
  constructor() {
    const serverURL = config.get('authDetails.SERVER_URL');
    let appRootURL:any = process.env.NODE_ENV === 'localhost' ? 'http://localhost:' + config.get('app.port') : config.get('app.rootUrl');
    if (!appRootURL.endsWith('/')) {
      appRootURL += '/';
    }  
    const appBaseURL = `${appRootURL}${config.get('globalPrefix')}`;
    const loginCallbackURL = `${appBaseURL}/auth/login/callback`;
    super({
      authorizationURL: `${serverURL}oauth2/authorize`,
      tokenURL: `${serverURL}oauth2/token`,
      clientID: config.get('CLIENT_ID'),
      //clientSecret: config.get('CLIENT_SECRET'),
      callbackURL: loginCallbackURL,
      scope: ['openid', 'profile'],
      state: (100000000000).toString(36)
    }, function(accessToken: string, refreshToken: string, params: any, profile: any, done: VerifyCallback) {
      done(null, {
        accessToken: accessToken
      });
    });
  }

NEST JS BearerStrategy code

export class BearerStrategy extends PassportStrategy(Strategy, 'bearer') {
    async validate(accessToken: string, done: VerifyCallback) {
      
      const user = {
            accessToken
        }
        done(null, user);
    }
        
    }

export class BearerStrategy extends PassportStrategy(Strategy, 'bearer') {
async validate(accessToken: string, done: VerifyCallback) {
  
  const user = {
        accessToken
    }
    done(null, user);
}
    
}

JWT strategy class

export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
      console.log(config.get('CLIENT_SECRET'))
      console.log(JSON.stringify(ExtractJwt.fromAuthHeaderAsBearerToken()))
    super({
      secretOrKey:  
      passportJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-XXXX/.well-known/jwks.json',
        
      }), 
      
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      //jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('JWT'),
     // ignoreExpiration: false,  
     // secretOrKey: config.get('CLIENT_SECRET'),
      algorithms:["RS256"],
      issuer : "https://cognito-idp.us-east-1.amazonaws.com/us-east-XXXX",
      audience: "client_id1234"
      
    });
  }
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
      console.log(config.get('CLIENT_SECRET'))
      console.log(JSON.stringify(ExtractJwt.fromAuthHeaderAsBearerToken()))
    super({
      secretOrKey:  
      passportJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-XXXX/.well-known/jwks.json',
        
      }), 
    
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      //jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('JWT'),
     // ignoreExpiration: false,  
     // secretOrKey: config.get('CLIENT_SECRET'),
      algorithms:["RS256"],
      issuer : "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXX",
      audience: "client_id12344"
      
    });
  }

now we are protecting them using auth guards

  @ApiTags("Root")
  @Get("/lookup")
  @UseGuards(AuthGuard("bearer"))
  async get() {
  return "got the data.."
  }

@ApiTags("Root")
@Get("/test")
@UseGuards(AuthGuard("jwt"))
async getSample() {
 return "got the data.."
}

Not sure why Passport validation didnt work validate method called from both Bearer and JWT strategy. while Bearer doesnt give an errors JWT throws "cb: is not a function" nt quite understood and not much help in web either.


Solution

  • this JwtStrategy should work. please replace aws-region and cognito-user-pool-id with yours.

    import { ExtractJwt, Strategy } from 'passport-jwt';
    import { PassportStrategy } from '@nestjs/passport';
    import { Injectable } from '@nestjs/common';
    import { HttpService } from '@nestjs/axios';
    import { map } from 'rxjs/operators';
    import find from 'lodash/find';
    import jwkToPem from 'jwk-to-pem';
    
    @Injectable()
    export class JwtStrategy extends PassportStrategy(Strategy) {
      constructor(private readonly httpService: HttpService) {
        super({
          jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
          secretOrKeyProvider: (_, token, done) => {
            const jwksUri =
              'https://cognito-idp.<aws-region>.amazonaws.com/<cognito-user-pool-id>/.well-known/jwks.json';
            this.httpService
              .get(jwksUri)
              .pipe(
                map((response) => {
                  const {
                    data: { keys },
                  } = response;
                  const tokenSections = (token || '').split('.');
                  if (tokenSections.length < 2) {
                    throw Error('something went wrong');
                  }
                  const headerJSON = Buffer.from(
                    tokenSections[0],
                    'base64',
                  ).toString('utf8');
                  const header = JSON.parse(headerJSON);
                  const jwk = find(keys, (key) => key.kid === header.kid);
                  if (!jwk) {
                    throw Error('something went wrong');
                  }
                  return jwkToPem(jwk);
                }),
              )
              .subscribe({
                next(pem) {
                  done(null, pem);
                },
                error(err) {
                  done(err.message, null);
                },
              });
          },
        });
      }
    
      async validate(payload: any) {
        return { userId: payload.sub, username: payload.username };
      }
    }