0

I am encountering an issue with Keycloak integration in my NestJS application. My environment uses Docker for orchestrating services. Here is an overview of my configuration and relevant files:

Context

  • Dockerfile:

    FROM node:18-alpine
    
    WORKDIR /app
    
    COPY package.json ./
    COPY package-lock.json ./
    
    RUN npm install
    
    COPY . .
    
    RUN npx prisma generate
    RUN npm run build
    
    EXPOSE 3000
    
    CMD ["node", "dist/main.js"]
    
  • docker-compose.yml:

    version: '3.8'
    services:
      nest-app:
        build:
          context: .
          dockerfile: Dockerfile
        ports:
          - "3000:3000"
        environment:
          DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/mydatabase"
        depends_on:
          postgres:
            condition: service_healthy
        networks:
          - my-network
    
      postgres:
        image: postgres:latest
        environment:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: mydatabase
        ports:
          - "5432:5432"
        volumes:
          - postgres_data:/var/lib/postgresql/data
        networks:
          - my-network
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U postgres"]
          interval: 10s
          timeout: 5s
          retries: 5
    
      keycloak:
        image: quay.io/keycloak/keycloak:latest
        environment:
          DB_VENDOR: h2
          KEYCLOAK_ADMIN: admin
          KEYCLOAK_ADMIN_PASSWORD: admin
          KEYCLOAK_IMPORT: /tmp/realm.json
        ports:
          - "8081:8080"
        volumes:
          - ./realm.json:/tmp/realm.json
        entrypoint: ["/opt/keycloak/bin/kc.sh"]
        command: ["start-dev"]
        networks:
          - my-network
    
    volumes:
      postgres_data:
    
    networks:
      my-network:
        driver: bridge
    
  • app.module.ts:

    import { Module } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import {
      AuthGuard,
      KeycloakConnectModule,
      ResourceGuard,
      RoleGuard,
    } from 'nest-keycloak-connect';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    import { PrismaModule } from './prisma.module';
    import { APP_GUARD } from '@nestjs/core';
    
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
        }),
        PrismaModule,
        KeycloakConnectModule.register({
          authServerUrl: 'http://127.0.0.1:8081/',
          realm: 'test',
          clientId: 'my-nest-project',
          secret: 'GSLUjhMMctvip6B01q3g7xu6P8SJBvT6', // or `credentials.secret` if used
        }),
      ],
      controllers: [AppController],
      providers: [
        AppService,
        {
          provide: APP_GUARD,
          useClass: AuthGuard,
        },
        {
          provide: APP_GUARD,
          useClass: ResourceGuard,
        },
        {
          provide: APP_GUARD,
          useClass: RoleGuard,
        },
      ],
    })
    export class AppModule {}
    
  • app.service.ts:

    import { Injectable } from '@nestjs/common';
    
    @Injectable()
    export class AppService {
      getHello(): string {
        return 'Hello, World!';
      }
    
      getProtectedMessage(): string {
        return 'This is a protected route';
      }
    
      getTestMessage(): string {
        return 'This is a test route';
      }
    }
    
  • main.ts:

    import { NestFactory } from '@nestjs/core';
    import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
    import { AppModule } from './app.module';
    import { PrismaService } from './prisma.service';
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
    
      // Swagger configuration
      const config = new DocumentBuilder()
        .setTitle('API Documentation')
        .setDescription('The API description')
        .setVersion('1.0')
        .addBearerAuth()
        .addTag('app')
        .addTag('hello')
        .build();
      const document = SwaggerModule.createDocument(app, config);
      SwaggerModule.setup('api', app, document);
    
      const prismaService = app.get(PrismaService);
      await prismaService.enableShutdownHooks(app);
    
      await app.listen(3000);
    }
    bootstrap();
    

Issue

When using Keycloak with my NestJS application, I get the following error in the Keycloak logs:

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

API Calls

  • Public Endpoints:

    • GET http://localhost:3000/ (works correctly)
    • GET http://localhost:3000/test (works correctly)
    • GET http://localhost:3000/hello (works correctly)
    • GET http://localhost:8081/realms/test (works correctly)
  • Get Token from Keycloak: (works correctly)

    • POST http://localhost:8081/realms/test/protocol/openid-connect/token
  • Access Protected Route with Bearer Token: (fails with 401 Unauthorized)

    • GET http://localhost:3000/protected

What I Tried

**Valid Token Check: ** I used the Keycloak admin interface to get a token and tried accessing the protected route. Expected: Successful access to the protected route with the valid token. Actual Result: Received a 401 Unauthorized response.

Different authServerUrl Configurations:

I tested various configurations for authServerUrl in app.module.ts:

Expected: Proper token validation and successful access to the protected route. Actual Result: The same 401 Unauthorized response with the wrong ISS error.

Recreating the Keycloak Realm: I attempted to recreate the Keycloak realm to ensure no configuration issues. Expected: Correctly configured realm leading to successful token validation. Actual Result: The issue persists with the same error.

Search on Stack Overflow: I searched for solutions on Stack Overflow using keywords related to this error, but I did not find a satisfactory answer. Expected: To find solutions or clues to resolve the token validation issue. Result: No conclusive answers found.

Questions

  1. What can I check or adjust to resolve this error?
  2. Are there additional configurations I should include in my Docker or NestJS setup to ensure proper integration with Keycloak?
1
  • This is common error occur when iss field in token not same domain with authServerUrl in server. Just config BE and FE point to same domain keycloak. Commented Oct 11, 2024 at 5:14

2 Answers 2

0

Problem is you must config keycloak URL from FE and BE is same. In BE, it only call keycloak over docker network with host http://keycloak:8080 but FE (web or postman) is host machine, that mean you config point to http://localhost:8081. This config make iss from token and server do not same.

In development mode, I think you shouldn't run BE inside docker, just run on your machine and update config BE and FE point to keycloak URL: http://localhost:8081.

So, if you want run every thing in Docker, you should config host in your machine just add below line in /etc/hosts

127.0.0.1 keycloak

and edit expose keycloak port 8080:8080. With this config, you can access http://keycloak:8080 from both inside (it point to container ip keycloak) and outside (it point to 127.0.0.1) docker , so should update keycloak url is 'http://keycloak:8080' at both BE and FE.

This way don't work if you setup network_mode: "host" in docker because it mount file /etc/hosts from machine to docker and BE can't call extract container keycloak.

Sign up to request clarification or add additional context in comments.

Comments

-1

I found this thread that fixed my problem.

To summarize: To request a token my frontend or postman (outside of container) will communicate with my container url (localhost) so the issuer will be localhost. But to verify the token, my backend (inside container) will communicate through the container network (in my case http://keycloak:8080). The issuer will then be different and the error will occured.

To fix you need to manually configure the Frontent url in keycloak admin console (in my case http://keycloak:8080)

2 Comments

It not working because http://keycloak:8080 just working in docker network, But FE run in browser on host machine.
@DoanThai Yes I have exactly the same problem : backend and keycloak on docker network and FE on local. As mentionned in the thread I linked, the solution may not be the best but just for or testing it worked for me

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.