1

I’m trying to configure Keycloak using Docker. The Keycloak is used for authentication in the angular frontend application, and also in the java spring backend application. My docker-compose configuration for these services, is as follows:

    backend:
        image: backend
        container_name: backend
        build:
            context: ./backend
        ports:
            - 8080:8081
        depends_on:
            - db
        networks:
            - net
        restart: always

    frontend:
        image: frontend
        container_name: frontend
        build:
            context: ./frontend
        ports:
            - 80:80
        depends_on:
            - backend
        networks:
            - net
        restart: always

    keycloak:
        image: quay.io/keycloak/keycloak:25.0.4
        command: start
        environment:
            KC_HOSTNAME_PORT: 8080
            KC_HTTP_ENABLED: true
            KC_HOSTNAME_STRICT_HTTPS: false
            KC_HEALTH_ENABLED: true
            KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN_USER}
            KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PWD}
            KC_DB: postgres
            KC_DB_URL: ${KEYCLOAK_DB_URL}
            KC_DB_USERNAME: ${KEYCLOAK_DB_USERNAME}
            KC_DB_PASSWORD: ${KEYCLOAK_DB_PWD}
            KC_HOSTNAME_STRICT: false
            KC_PROXY: edge
            KC_HOSTNAME: http://keycloak:8080
            KC_HOSTNAME_BACKCHANNEL_DYNAMIC: true
        ports:
            - 8083:8080
        restart: always
        networks:
            - net

    db:
        image: 'postgres'
        container_name: db
        environment:
            - POSTGRES_USER=${POSTGRES_USER}
            - POSTGRES_PASSWORD=${POSTGRES_PWD}
        ports:
            - "5432:5432"
        networks:
            - net

The spring property is this:

spring.security.oauth2.resourceserver.jwt.issuer-uri=http://keycloak:8080/realms/<realm-name>

And in the angular, I’m using the following configuration:

config: {
    url: 'http://localhost:8083',
    realm: "<realm-name>",
    clientId: "<client-id>"
}

Authentication in the backend works well, but the problem is the authentication in the frontend application, because as the keycloak hostname is “keycloak”, in frontend application it doesn’t know this hostname. If I change it to localhost, the backend container cannot connect to the keycloak container.

Does anyone know how I can solve this problem?

1
  • I can hardly believe you couldn't solve your problem with my answer. Commented Oct 22, 2024 at 23:17

1 Answer 1

2

Why

We should use the same value for the authorization server hostname across all actors because of OIDC discovery and OpenID tokens validation specifications which require that the Issuer Identifier must be the exact same in:

  • the discovery endpoint URI: {Issuer Identifier}/.well-known/openid-configuration
  • the issuer property of OpenID configuration (exposed at the discovery endpoint)
  • the iss claim in JWTs and introspection endpoint

For Keycloak, the Issuer Identifier value depends on the hostname configuration property - or request URI if hostname-strict=false.

As it is used by your Spring resource server (during OIDC discovery and access token validation) and Angular (during authorization code flow and, because you configured Angular as a public OAuth2 client, during OIDC discovery and ID token validation), you should set hostname with a value known within Docker containers and the host that runs the browser you are displaying the SPA with.

As you seem to be using a reverse proxy (edge which, by the way, was removed in the latest releases), I'd use a URI through that reverse proxy (with care to the hostname, but also scheme and port). I'd also set hostname-strict=true to ensure that everything uses only that URI.

keycloak is known as hostname only within Docker, which is the reason why http://keycloak:8080 works for the Spring container but not the browser on the Docker host machine. loclahost works within the browser because you expose Keycloak on the host machine, but for the Spring container, localhost loops to itself, not to the host.

Solutions

Use a hostname known everywhere

In this article I wrote as an introduction to Keycloak with Spring, I use the value of the hostname command in a shell prompt on the host machine (on Widows, using Git Bash). The output of this command should be known everywhere.

In the case where it wouldn't be known by Docker containers, you can add to extra_hosts. Sample in a Docker compose file (replace {hostname}):

services:
  backend:
    extra_hosts:
    - "{hostname}:host-gateway"

Disable OIDC discovery and issuer validation

Another option - that I recommend not to use - is to set the Issuer Identifier hostname with a value working for the frontend (localhost for instance), and to disable OIDC discovery and JWT issuer validation in Spring: instead of relying on the OpenID auto-configuration from the issuer-uri, leave it empty and manually set all other provider properties.

Configure the host machine to resolve Docker services

A last option is to add the following entry in the hosts file of the host machine (depending on the OS, /etc/hosts or C:\Windows\System32\drivers\etc\hosts):

127.0.0.1 keycloak

Important side note

For security reasons, it is now recommended to use only confidential clients (those using a secret and running on the backend). So, the OAuth2 client should not be your Angular app but a middleware on the backend. I wrote another article for that. Note that this OAuth2 BFF pattern still uses the authorization code flow. So, this does not remove the need for the browser to redirect to the authorization server - and for the hostname in the Issuer Identifier to be resolvable from all the involved hosts and containers.

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

5 Comments

Sorry for the delay in responding. I was trying to follow the approach I already had (oauth2 login in angular app), with the proxy configuration, but without success, I can open the keycloak login page through the reverse proxy, but after login it redirects to the name of the defined host, which is the name of the docker container (keycloak), and obviously this name is not known by the browser. Anyway, thank you very much for the detailed answer, and for the tip on improving security by removing the oauth2 login from the angular application, which I will try later.
The hostname of the keycloak that I am putting in the configuration is the name of the docker container, that is, keycloak, but it's not recognized in the terminal because it is outside the docker environment. And what I'm trying is to put a reverse proxy, for example when invoking localhost:<port>, forward requests to the keycloak docker container, and this works, because the keycloak login page appears, but then After logging in, the redirection in the browser is to the hostname keycloak instead of localhost.
This what I explained in the answer. So you did not try what I told. Just type hostname on a shell prompt and use the output as value for hostname in your configuration files.
Sorry, I definitely didn't understand. I tried this, and it works for log in into the frontend app (just like it worked with the localhost hostname), but then I have the problem on the backend container of not knowing that hostname within docker.
Thank you very much for your help. Adding the 127.0.0.1 keycloak in the /etc/hosts solved the problem

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.