Two Keycloaks on local machine to test brokering

I am trying to test a scenario of 2 keycloaks where one is identity provider for the other, but I keep getting a 502 error with both them on localhost… is it possible?

Here is my simple testcase:

docker-compose.yml

services:
  keycloak1:
    image: "quay.io/keycloak/keycloak:latest"
    ports:
      - "1234:8080"
    environment:
      KEYCLOAK_USER: admin1
      KEYCLOAK_PASSWORD: admin
  keycloak2:
    image: "quay.io/keycloak/keycloak:latest"
    ports:
      - "4321:8080"
    environment:
      KEYCLOAK_USER: admin2
      KEYCLOAK_PASSWORD: admin

Since I am a new user, I cant post pictures, I will describe the steps textually, for a more visual bug report see here Keycloak Discourse Bug Report · Issue #9 · stenagam/demo-federated-module-login · GitHub

Keycloak 1 (http://localhost:1234 )

  • new realm named realm_a
  • user registration enabled, SSL disabled
  • new client named keycloak2
    • access type: confidential
    • wildcards on Valid Redirect URIs and Web Origins:
      • Valid Redirect URIs: http://localhost:4321/*
      • Web Origins: *
    • client secret copied from “Credentials” tab

finishing with admin1 Sign Out

Keycloak 2 (http://localhost:4321 )

  • new realm named realm_b
  • user registration On, SSL Off
  • new identity provider Keycloak OpenID Connect
    • auth endpoint: http://localhost:1234/auth/realms/realm_a/protocol/openid-connect/auth
    • token endpoint: http://localhost:1234/auth/realms/realm_a/protocol/openid-connect/token
    • Client Authentication: Client secret sent as post
    • Client ID: keycloak2
    • Client Secret: <paste>

Save and SignOut admin2

Keycloak 1 from user perspective
Open http://localhost:1234/auth/realms/realm_a/account and SignIn to register a new user.

After success login, signout

Keycloak 2 from user perspective
Open http://localhost:4321/auth/realms/realm_b/account and attempt to Sign In using the Identity Provider.

After click on “keycloak1” button, sign in. The session is open on keycloak 1 but the redirect is to an error page.

We are sorry...
Unexpected error when authenticating with identity provider

Logs

keycloak2_1  | 12:31:38,803 WARN  [org.keycloak.connections.httpclient.DefaultHttpClientFactory] (default task-13) TruststoreProvider is disabled
keycloak2_1  | 12:31:38,889 ERROR [org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (default task-13) Failed to make identity provider oauth callback: org.apache.http.conn.HttpHostConnectException: Connect to localhost:1234 [localhost/127.0.0.1] failed: Connection refused (Connection refused)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:156)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
keycloak2_1  | 	at org.keycloak.keycloak-server-spi-private@15.0.2//org.keycloak.broker.provider.util.SimpleHttp.makeRequest(SimpleHttp.java:277)
keycloak2_1  | 	at org.keycloak.keycloak-server-spi-private@15.0.2//org.keycloak.broker.provider.util.SimpleHttp.asResponse(SimpleHttp.java:216)
keycloak2_1  | 	at org.keycloak.keycloak-server-spi-private@15.0.2//org.keycloak.broker.provider.util.SimpleHttp.asString(SimpleHttp.java:208)
keycloak2_1  | 	at org.keycloak.keycloak-services@15.0.2//org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider$Endpoint.authResponse(AbstractOAuth2IdentityProvider.java:500)
keycloak2_1  | 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
keycloak2_1  | 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
keycloak2_1  | 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
keycloak2_1  | 	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:138)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:546)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:435)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$0(ResourceMethodInvoker.java:396)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:398)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:365)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:150)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:110)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:141)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:104)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:440)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:245)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:61)
keycloak2_1  | 	at org.jboss.resteasy.resteasy-jaxrs@3.15.1.Final//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
keycloak2_1  | 	at javax.servlet.api@2.0.0.Final//javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
keycloak2_1  | 	at org.keycloak.keycloak-wildfly-extensions@15.0.2//org.keycloak.provider.wildfly.WildFlyRequestFilter.lambda$doFilter$0(WildFlyRequestFilter.java:41)
keycloak2_1  | 	at org.keycloak.keycloak-services@15.0.2//org.keycloak.services.filters.AbstractRequestFilter.filter(AbstractRequestFilter.java:43)
keycloak2_1  | 	at org.keycloak.keycloak-wildfly-extensions@15.0.2//org.keycloak.provider.wildfly.WildFlyRequestFilter.doFilter(WildFlyRequestFilter.java:39)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
keycloak2_1  | 	at org.wildfly.extension.undertow@23.0.2.Final//org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
keycloak2_1  | 	at org.wildfly.extension.undertow@23.0.2.Final//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
keycloak2_1  | 	at org.wildfly.extension.undertow@23.0.2.Final//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
keycloak2_1  | 	at org.wildfly.extension.undertow@23.0.2.Final//org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
keycloak2_1  | 	at org.wildfly.extension.undertow@23.0.2.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
keycloak2_1  | 	at org.wildfly.extension.undertow@23.0.2.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
keycloak2_1  | 	at org.wildfly.extension.undertow@23.0.2.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
keycloak2_1  | 	at org.wildfly.extension.undertow@23.0.2.Final//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
keycloak2_1  | 	at io.undertow.servlet@2.2.5.Final//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.server.Connectors.executeRootHandler(Connectors.java:387)
keycloak2_1  | 	at io.undertow.core@2.2.5.Final//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:841)
keycloak2_1  | 	at org.jboss.threads@2.4.0.Final//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
keycloak2_1  | 	at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990)
keycloak2_1  | 	at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
keycloak2_1  | 	at org.jboss.threads@2.4.0.Final//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
keycloak2_1  | 	at org.jboss.xnio@3.8.4.Final//org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280)
keycloak2_1  | 	at java.base/java.lang.Thread.run(Thread.java:829)
keycloak2_1  | Caused by: java.net.ConnectException: Connection refused (Connection refused)
keycloak2_1  | 	at java.base/java.net.PlainSocketImpl.socketConnect(Native Method)
keycloak2_1  | 	at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:399)
keycloak2_1  | 	at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:242)
keycloak2_1  | 	at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:224)
keycloak2_1  | 	at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
keycloak2_1  | 	at java.base/java.net.Socket.connect(Socket.java:609)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:75)
keycloak2_1  | 	at org.apache.httpcomponents.core//org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
keycloak2_1  | 	... 90 more
keycloak2_1  | 
keycloak2_1  | 12:31:38,924 WARN  [org.keycloak.events] (default task-13) type=IDENTITY_PROVIDER_LOGIN_ERROR, realmId=realm_b, clientId=account-console, userId=null, ipAddress=172.24.0.1, error=identity_provider_login_failure, code_id=ff4c5f88-7047-4462-ac77-4f7912655874, authSessionParentId=ff4c5f88-7047-4462-ac77-4f7912655874, authSessionTabId=QYSgucrsGvc

Browser Network Tab
(have a 502 request in it)

What am I missing? Is it possible to have the identity provider and the broker on the same machine?

Thanks!

I would guess you are seeing an effect of the infinispan detection. See Keycloak 15.0.2 docker image issue - #4 by xgp

1 Like

And what would be the proper workaround? use and older version of keycloak? which one is a good bet? Thanks for the help!

I guess running the instances in from two different docker-compose files should work. Or you need to assign two different internal networks that are isolated.

2 docker-compose.yaml files didnt work :frowning:

the logs on the stackoverflow post have mentions of infinispan, my logs dont, I wonder if this is a different issue

keycloak2_1  | 12:31:38,889 ERROR [org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (default task-13) Failed to make identity provider oauth callback: org.apache.http.conn.HttpHostConnectException: Connect to localhost:1234 [localhost/127.0.0.1] failed: Connection refused (Connection refused)

You should get an understanding of how hostnames (and localhost) behave in dockerized environments.
localhost inside docker is not the same as on the host machine.

1 Like

My attempt of isolating the networks:

docker-compose.yml:

networks:
  net1:
    driver: "bridge"
  net2:
    driver: "bridge"
services:
  keycloak1:
    image: "quay.io/keycloak/keycloak:latest"
    networks:
      - net1
    ports:
      - "127.0.0.1:1234:8080"
    environment:
      KEYCLOAK_USER: admin1
      KEYCLOAK_PASSWORD: admin
  keycloak2:
    image: "quay.io/keycloak/keycloak:latest"
    networks:
      - net2
    ports:
      - "127.0.0.1:4321:8080"
    environment:
      KEYCLOAK_USER: admin2
      KEYCLOAK_PASSWORD: admin

and the results of docker nework inspect for each one after docker-compose up:

net1

[
    {
        "Name": "keycloak-broker_net1",
        "Id": "f20c14b1df11cf5bea8efcbcd1ce75380a438dbfa5fabd192ebc3deae9c2103b",
        "Created": "2021-10-06T08:51:33.790035592-03:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "192.168.64.0/20",
                    "Gateway": "192.168.64.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "97bdc8da52a30e6de9c3f6afc234c24123e9cded99e09bb445dff8811e47b1dd": {
                "Name": "keycloak-broker_keycloak1_1",
                "EndpointID": "a4398d6d78bf07ff5f7b2b1971422992b63b449438d8ee23f2ce3a41f2f03227",
                "MacAddress": "02:42:c0:a8:40:02",
                "IPv4Address": "192.168.64.2/20",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "net1",
            "com.docker.compose.project": "keycloak-broker",
            "com.docker.compose.version": "1.29.0"
        }
    }
]

net2

[
    {
        "Name": "keycloak-broker_net2",
        "Id": "68e3ecf956c2f193726e048bb79d17f80bc843919b374b073a97744605039920",
        "Created": "2021-10-06T08:51:33.839702566-03:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "192.168.80.0/20",
                    "Gateway": "192.168.80.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "3c440bbbafd6a1a6490941da7741ebfbea580c1b882880df48b4904a23feb22d": {
                "Name": "keycloak-broker_keycloak2_1",
                "EndpointID": "2150427f9a99a474dd1adbd540068522a202425171ac4ac878f8ef2a006ff795",
                "MacAddress": "02:42:c0:a8:50:02",
                "IPv4Address": "192.168.80.2/20",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "net2",
            "com.docker.compose.project": "keycloak-broker",
            "com.docker.compose.version": "1.29.0"
        }
    }
]

FWIW I have 2 Keycloaks running from containers (at first directly from docker, now I’m running with kubernetes).

I can"t login to both in the same browser. Is this your problem?
I use “new private window” in the browser, or even 2 different browsers. It works fine.

My curl calls to both Keycloak servers also work fine.

BTW, I’m doing the same kind of thing, but, correct me if I’m wrong, it’s also possible to proof-of-concept brokering simply with one Keycloak and 2 realms. The realms are just as isolated as 2 Keycloak servers, aren’t they?

1 keycloak with 2 realms could work, I will try that, thanks!!

1 Like

Did you succed with one keycloak and two realms?

I have tried to do this, but at the end when I think everything is set up correctly and I’m trying to get an access token via the Authorization Code grant, I’m getting a “Code not valid” error

{"error":"invalid_grant","error_description":"Code not valid"}

I’ve prepared my setup with docker compose and terraform, see GitHub - Brackmeister/keycloak_brokering_tests: Testing identity brokering with two keycloak realms in a docker environment

The summary of this setup where the keycloak container is running on 192.168.178.42 with port 8103:

  • one realm called “identity_provider” with a confidential client called “idp_client” and a test user (user/pw = “user”/“user”)
  • a second realm called “user_facing” with an keycloak-oidc identity provider called “idp” that uses above “idp_client” and its secret plus a public client called “frontend” to simulate a login via an web app (not in my sample code)

Then I’m using Postman with an OAuth 2.0 token configuration like this

  • Grant Type: Authorization Code
  • Callback URL: http://192.168.178.42:8103/realms/user_facing/.well-known/openid-configuration (not sure how relevant this is for testing purposes)
  • Auth URL: http://192.168.178.42:8103/realms/user_facing/protocol/openid-connect/auth
  • Access Token URL: http:// 192.168.178.42:8103/realms/user_facing/protocol/openid-connect/token
  • Client ID: frontend

When I then try to get a fresh token via Postman, this is what happens:

  1. Click “Get New Access Token” in Postman
  2. In the window that opens click the “idp” button below “Or sign in with” (on realm “user_facing”)
  3. Enter Username user and Password user and click the “Sign In” button (on realm “identity_provider”)
  4. Instead of getting a token, I receive the {"error":"invalid_grant","error_description":"Code not valid"} error

The Postman console shows these requests

GET http://192.168.178.42:8103/realms/user_facing/protocol/openid-connect/auth?response_type=code&client_id=frontend&redirect_uri=http%3A%2F%2F192.168.178.42%3A8103%2Frealms%2Fuser_facing%2F.well-known%2Fopenid-configuration
GET http://192.168.178.42:8103/realms/user_facing/broker/idp/login?client_id=frontend&tab_id=7YSUnIuj6eE&session_code=9QjgFWslY_mpubqCnvKe7QKDN-x91mAGzBLoUpCY9iU
GET http://192.168.178.42:8103/realms/identity_provider/protocol/openid-connect/auth?scope=openid&state=qUWrCfVh1JIOU3kVGn3dGYJdV0WAxdYTrg6W378kPag.7YSUnIuj6eE.BL8tqZBkRGWmWWvunAxNwQ&response_type=code&client_id=idp_client&redirect_uri=http%3A%2F%2F192.168.178.42%3A8103%2Frealms%2Fuser_facing%2Fbroker%2Fidp%2Fendpoint&nonce=imvdddMjvHv5Vo3g3v_5jA
POST http://192.168.178.42:8103/realms/identity_provider/login-actions/authenticate?session_code=aOXAJWIrqXd16eIGmcHsLIUgdQwv3sfdGb17WW8PceM&execution=db3997b3-bde5-4abc-bb4e-037d21396f93&client_id=idp_client&tab_id=a-Wy9AAiu-M
POST http://192.168.178.42:8103/realms/user_facing/protocol/openid-connect/token
  grant_type: "authorization_code"
  code: "d20e079b-0cad-4945-bfe8-970ec1f64ccd.b6eafb12-8965-4959-9069-f3939c2354ae.cafc4a19-11bb-46cf-8779-f7270d75bfae"
  redirect_uri: "http://192.168.178.42:8103/realms/user_facing/.well-known/openid-configuration"

And that last token request gives the “Code not valid” error.

The docker logs show

type=CODE_TO_TOKEN_ERROR, realmId=user_facing, clientId=frontend, userId=null, ipAddress=192.168.178.42, error=invalid_code, grant_type=authorization_code, code_id=b6eafb12-8965-4959-9069-f3939c2354ae, client_auth_method=client-secret

The code_id from the log is only the middle part of the code that was sent in the request body.
Is that the problem?
But how to fix this?

Ok, by chance I found how to get the Postman authorization to work: the correct “Callback URI” is https://oauth.pstmn.io/v1/callback which I already had as an allowed redirect URI in the “frontend” client of the “user_facing” realm from previous tests.

But I can’t say I understand why this is working while http://192.168.178.42:8103/realms/user_facing/.well-known/openid-configuration or http://192.168.178.42:8103/realms/user_facing/broker/idp/endpoint didn’t.

The callback uri is where keycloak redirects to to transfer the initial authorization code, so if you transfer it to postman, then it can not work.

I wasn’t sure if I understood you correctly, so I did a small A/B test: fetching the access token with correct callback URL and one that didn’t work plus comparing the output in the postman console.

It looked roughly like this for both tests

GET http://192.168.178.42:8103/realms/user_facing/protocol/openid-connect/auth
GET http://192.168.178.42:8103/realms/user_facing/broker/idp/login
GET http://192.168.178.42:8103/realms/identity_provider/protocol/openid-connect/auth
POST http://192.168.178.42:8103/realms/identity_provider/login-actions/authenticate

In case of the wrong callback URL that was followed by the token call with the 403 “Invalid Code” message

POST http://192.168.178.29:8103/realms/user_facing/protocol/openid-connect/token

With the correct callback URL there was an additional request

GET http://192.168.178.42:8103/realms/user_facing/broker/idp/endpoint
    "location": "https://oauth.pstmn.io/v1/callback?session_state=6ca2306d-9776-42e6-b9ee-63cee8be9e01&code=5ca336b1-1444-49e2-81f7-c2adfa92322c.6ca2306d-9776-42e6-b9ee-63cee8be9e01.e08f1df6-8fa8-4569-b4c7-a7fbb93db238",
POST http://192.168.178.42:8103/realms/user_facing/protocol/openid-connect/token

I think the trouble I had in understanding the difference was that in the case where it doesn’t work, postman sends the same code again that was used when the “user_facing” realm was authenticating against the “identity_provider” realm, because both realms are on the same host. I was somehow expecting that it just wouldn’t have a code at all in a wrong setup.

But, yes, without the call to https://oauth.pstmn.io/v1/callback (which has some JS to execute postman://app/oauth2/callback with the same URL parameters) the final authorization code can’t be known by postman.

Hello, I try to apply a similar approach on my local machine to get a better understanding of the idp workflow and as well to have a local environment for testing.

I used a similar approach as described above to setup two different keycloak instances:

version: '3'
services:
  db:
    image: docker.io/mariadb:10.7.1
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: keycloak
      MYSQL_USER: keycloak
      MYSQL_PASSWORD: pass
    ports:
      - 3306:3306
    volumes:
      - database-data:/var/lib/mysql
      - /init.sql:/docker-entrypoint-initdb.d/init.sql

  keycloak-broker:
    image: quay.io/keycloak/keycloak:23.0
    command: start-dev
    environment:
      KC_DB: mariadb
      KC_DB_URL_HOST: db
      KC_DB_URL_DATABASE: keycloak_broker
      KC_DB_USERNAME: root
      KC_DB_PASSWORD: root
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: changepwd
      HTTP_ADDRESS_FORWARDING: ${PROXY_ADDRESS_FORWARDING:-true}
    depends_on:
      - db
    ports:
      - ${KEYCLOAK_PORT:-8080}:8080
    extra_hosts:
      - "host.docker.internal:host-gateway"

  keycloak-provider:
    image: quay.io/keycloak/keycloak:23.0
    command: start-dev
    environment:
      KC_DB: mariadb
      KC_DB_URL_HOST: db
      KC_DB_URL_DATABASE: keycloak_provider
      KC_DB_USERNAME: root
      KC_DB_PASSWORD: root
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: changepwd
      HTTP_ADDRESS_FORWARDING: ${PROXY_ADDRESS_FORWARDING:-true}
    depends_on:
      - db
    ports:
      - ${KEYCLOAK_PORT:-8000}:8080
    extra_hosts:
      - "keycloak.localtest.me:host-gateway"

volumes:
  database-data:

As you can see, I also defined an external host keycloak.localtest.me which is also listed in my hosts file as

127.0.0.1 keycloak.localtest.me

I configured a client in the provider which defines a redirect to http://keycloak.localtest.me:8080/realms/idp-broker/broker/keycloak-idp/endpoint.

The keycloak broker is configured to connect to the given client above also using the host keycloak.localtest.me.

When I open a new cognito browser and try to login via http://keycloak.localtest.me:8000/realms/idp-provider/account/ I can select my defined idp. When I click on the idp I also get forwarded to the keycloak provider and can enter the credentials. After the entering the correct credentials the browser tries to redirect back to the broker and I receive the error Unexpected error when authenticating with identity provider. In the logs of the Broker I get the error:

2024-02-27 8:00:50 security-keycloak-broker-1    | 2024-02-27 8:00:50,473 ERROR [org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (executor-thread-80) Failed to make identity provider oauth callback: org.apache.http.conn.HttpHostConnectException: Connect to keycloak.localtest.me:8000 [keycloak.localtest.me/127.0.0.1] failed: Connection refused
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:156)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.keycloak.broker.provider.util.SimpleHttp.makeRequest(SimpleHttp.java:299)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.keycloak.broker.provider.util.SimpleHttp.asResponse(SimpleHttp.java:229)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider$Endpoint.authResponse(AbstractOAuth2IdentityProvider.java:517)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider$Endpoint$quarkusrestinvoker$authResponse_fef2d69ce31937f365a37fb3083f9247bc4c56d2.invoke(Unknown Source)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:145)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at java.base/java.lang.Thread.run(Thread.java:840)
2024-02-27 8:00:50 security-keycloak-broker-1    | Caused by: java.net.ConnectException: Connection refused
2024-02-27 8:00:50 security-keycloak-broker-1    |     at java.base/sun.nio.ch.Net.connect0(Native Method)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at java.base/sun.nio.ch.Net.connect(Net.java:579)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at java.base/sun.nio.ch.Net.connect(Net.java:568)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:593)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at java.base/java.net.Socket.connect(Socket.java:633)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:75)
2024-02-27 8:00:50 security-keycloak-broker-1    |     at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
2024-02-27 8:00:50 security-keycloak-broker-1    |     ... 24 more
2024-02-27 8:00:50 security-keycloak-broker-1    | 
2024-02-27 8:00:50 security-keycloak-broker-1    | 2024-02-27 8:00:50,473 WARN  [org.keycloak.events] (executor-thread-80) type="IDENTITY_PROVIDER_LOGIN_ERROR", realmId="09a60ab7-727c-431f-8586-87f398540b65", clientId="account-console", userId="null", ipAddress="192.168.48.1", error="identity_provider_login_failure", code_id="5ff8dc58-c1eb-4b5d-aac6-d7059c7f9f3c"

Unlike in the post before, I think I handled the domain name conflict by defining the external host as keycloak.localtest.me. This seems also to work since I can access both instances from either inside of the docker network or from my local machine using the browser with the same address. But maybe I miss something crucial in my configuration, or I lack in the protocol understanding.

Does someone have an idea how to get the described scenario up and running.
Cheers