Search code examples
nestjskeycloaknestjs-passportnestjs-jwt

keyCloak: (wrong ISS) or (wrong audience), in NestJS


I want to validate the JWT token for all the controller access points buy using KeyCloack.

Sometimes I get this error:

WARN [Keycloak] Cannot validate access token: Error: Grant validation failed. Reason: invalid token (wrong audience)

or

WARN [Keycloak] Cannot validate access token: Error: Grant validation failed. Reason: invalid token (wrong ISS)

I'm sending the token as a bearer token in the header

  1. What is the difference between wrong ISS and wrong audience error?
  2. Why do I'm getting this?
  3. Any suggestions to resolve it?

I'm new with NestJS and Keycloak, please guide me if I missed something.

My approach:

env variable:

They keyCloak is deployed on a server whose URL is below:

## Authentication-related values
PEM_KEY_PATH='./src/auth/user-service.pem'
KEYCLOAK_AUTH_SERVER_URL='http://service.serveraws.io/realms/user-service'
KEYCLOAK_AUTH_SERVER_URL='http://service.serveraws.io/'
KEYCLOAK_REALM='user-service'
KEYCLOAK_CLIENT_ID='user-kundali'
KEYCLOAK_SECRET='test'

auth.module.ts

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import {
  KeycloakConnectModule,
  RoleGuard,
  AuthGuard,
  PolicyEnforcementMode,
  TokenValidation,
} from 'nest-keycloak-connect';

import * as dotenv from "dotenv";
dotenv.config();

@Module({
  imports: [ KeycloakConnectModule.register({
    authServerUrl: process.env.KEYCLOAK_AUTH_SERVER_URL,
    realm: process.env.KEYCLOAK_REALM,
    // clientId: process.env.KEYCLOAK_CLIENT_ID,
    resource: process.env.KEYCLOAK_CLIENT_ID,
    verifyTokenAudience: true,
    secret: process.env.KEYCLOAK_SECRET,
    policyEnforcement: PolicyEnforcementMode.PERMISSIVE,
    tokenValidation: TokenValidation.ONLINE,
  }), ],
  controllers: [],
  providers: [
    {
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
    {
      provide: APP_GUARD,
      useClass: RoleGuard,
    }
  ],
})

export class KeyCloakModule {
}

app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import validationSchema from './config.schema';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';
import { KeyCloakModule } from './auth/auth.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      name: 'default',
      type: 'mongodb',
      database: 'test-db',
      //url: 'mongodb://localhost:27017',
      useNewUrlParser: true,
      autoLoadEntities: true,
      useUnifiedTopology: true,
      synchronize: true,
      entities: [],
      ssl: true,
      retryWrites: false
    }),
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema,
    }),
    UserModule,
    KeyCloakModule
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

my custom Jwt Auth instead of using authGuard: jwtGuard.service.ts

I'm using pem key instead of the secret phrase, but I have tried with both the secret phrase and pem key, getting the same error.

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { readFileSync } from 'fs';
import path from 'path';
import * as dotenv from "dotenv";
dotenv.config();

const filePath = process.cwd();
const key = readFileSync(path.resolve(filePath, process.env.PEM_KEY_PATH));

@Injectable()
export class JwtAuthGuard extends PassportStrategy(Strategy) {
  constructor(private configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: key,
    });
  }

  async validate(payload: any) {
    console.log(" payload ::: ",payload)

    return { ...payload.user };
  }
}

my user controller: user.controller.ts

import { Response } from 'express';
import {
  Res, HttpStatus,
  Controller, Get, Param, Query, UseGuards
} from '@nestjs/common';
import {
  ApiBearerAuth,
  ApiCreatedResponse,
  ApiOperation,
  ApiTags,
  ApiParam,
  ApiQuery,
} from '@nestjs/swagger';
import { UserServices } from './users.services';
import { UserModel } from './entity/user.entity';
import { AuthGuard, Roles, Unprotected } from 'nest-keycloak-connect';
import { JwtAuthGuard } from '../auth/jwtAuthGuard.service';

@ApiBearerAuth()
@ApiTags('Reports')
@Controller('reports')

export class UserController {
  constructor(
    private readonly userServices: UserServices
  ) { }

  // @Unprotected()
  @Get('/user-data/:username')
  // @Roles({ roles: ['admin', 'other', 'realm:sysadmin'] })
  @UseGuards(JwtAuthGuard)
  @ApiOperation({ summary: 'Fetch kundali of a user.' })
  @ApiParam({
    name: 'username',
    description: 'username',
    required: true,
  })
  @ApiCreatedResponse({ type: UserModel, description: `fetch all the the data` })
  async getUserKundali(
    @Res() res: Response,
    @Param("username") username: string
  ): Promise<any> {

    const response = await this.userServices.getUserKundali(username);

    if (response && response.data) {
      return res.status(HttpStatus.OK).send(response);
    } else {
      return res.status(HttpStatus.BAD_REQUEST).send(response);
    }
  }
}

Solution

  • Change your KEYCLOAK_AUTH_SERVER_URL to 'http://service.serveraws.io/auth/'.

    It must end with /auth/.