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?
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:
{Issuer Identifier}/.well-known/openid-configuration
issuer
property of OpenID configuration (exposed at the discovery endpoint)iss
claim in JWTs and introspection endpointFor 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.
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"
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.
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
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.