0
votes

I've got an issue with a new Keycloak installation that I'm working on.

I've got a PHP-based app which authenticates users via Keycloak. This works fine as long as I use local users (users stored in the Keycloak-realm).

I want the users to be able to authenticate through an external IdP though. To achieve this, I've added this OIDC-IdP through the Keycloak Admin interface.

Everything works fine the first time a user logs in. If the user logs out and reattempts a login, Keycloak shows an error page: "We are sorry... Invalid username or password."

The logging at this point shows the following:

15:04:06,537 WARN  [org.keycloak.services] (default task-61) KC-SERVICES0013: Failed authentication: org.keycloak.authentication.AuthenticationFlowException
    at [email protected]//org.keycloak.authentication.AuthenticationProcessor.authenticateOnly(AuthenticationProcessor.java:981)
    at [email protected]//org.keycloak.services.resources.LoginActionsService$1.authenticateOnly(LoginActionsService.java:798)
    at [email protected]//org.keycloak.authentication.AuthenticationProcessor.authenticate(AuthenticationProcessor.java:840)
    at [email protected]//org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:313)
    at [email protected]//org.keycloak.services.resources.LoginActionsService.brokerLoginFlow(LoginActionsService.java:828)
    at [email protected]//org.keycloak.services.resources.LoginActionsService.firstBrokerLoginGet(LoginActionsService.java:722)
    at jdk.internal.reflect.GeneratedMethodAccessor780.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at [email protected]//org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:138)
    at [email protected]//org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:543)
    at [email protected]//org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:432)
    at [email protected]//org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$0(ResourceMethodInvoker.java:393)
    at [email protected]//org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
    at [email protected]//org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:395)
    at [email protected]//org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:364)
    at [email protected]//org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:150)
    at [email protected]//org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:104)
    at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:440)
    at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
    at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
    at [email protected]//org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
    at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
    at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
    at [email protected]//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:245)
    at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:61)
    at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
    at [email protected]//javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
    at [email protected]//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
    at [email protected]//org.keycloak.provider.wildfly.WildFlyRequestFilter.lambda$doFilter$0(WildFlyRequestFilter.java:41)
    at [email protected]//org.keycloak.services.filters.AbstractRequestFilter.filter(AbstractRequestFilter.java:43)
    at [email protected]//org.keycloak.provider.wildfly.WildFlyRequestFilter.doFilter(WildFlyRequestFilter.java:39)
    at [email protected]//io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at [email protected]//io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
    at [email protected]//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at [email protected]//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at [email protected]//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at [email protected]//org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
    at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at [email protected]//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
    at [email protected]//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
    at [email protected]//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at [email protected]//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at [email protected]//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at [email protected]//io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at [email protected]//io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at [email protected]//io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
    at [email protected]//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at [email protected]//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
    at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at [email protected]//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
    at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
    at [email protected]//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at [email protected]//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at [email protected]//org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
    at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
    at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
    at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
    at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
    at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
    at [email protected]//io.undertow.server.Connectors.executeRootHandler(Connectors.java:370)
    at [email protected]//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
    at [email protected]//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at [email protected]//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982)
    at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
    at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
    at java.base/java.lang.Thread.run(Thread.java:834)

15:04:06,539 WARN  [org.keycloak.events] (default task-61) type=IDENTITY_PROVIDER_FIRST_LOGIN_ERROR, realmId=<REDACTED>, clientId=<REDACTED>, userId=null, ipAddress=<REDACTED>, error=invalid_user_credentials, identity_provider=oidc, auth_method=openid-connect, redirect_uri=<REDACTED>, identity_provider_identity=<REDACTED>, code_id=4efad092-1ab8-4576-a7ca-d289cb427fd0, authSessionParentId=4efad092-1ab8-4576-a7ca-d289cb427fd0, authSessionTabId=_xkOGlSB9eo

I'm using a simple login flow as described on this page for my 'First login flow'. My 'Post login flow' is empty as per default. If I use the default 'First Broker Login', I get a screen with the message "Account already exists", "How do you want to continue?". I don't want this screen, I want the user just to be able to continue to the application like it was able to the first time he/she logs in.

As everything is working fine the first time a user logs in, I think it has got something to do with the fact that the user already exists in Keycloak. If I look at the user, I can see it is successfully linked to the identity provider, so I'm clueless as to why this would prove to be a problem.

Edit: As requested by @dreamcrash, a screenshot of the IdP configuration IdP config Note: I had to redact our URL and Client ID in this image

1

1 Answers

1
votes

As already stated in the comments of the original post, I've found the solution and it had nothing to do with Keycloak.

My IdP gives the option to switch between transient and persistent sub-claims in the OIDC-token. I had it set on transient, which means that the OIDC-token will have a different claim each time the user logs in.

If that's the case, Keycloak looks at the email address in the token to determine whether the user already exists and gives the user the option to merge both accounts (if you use the default First Broker Login-flow). This will then happen each time the user gets a new transient ID in the token (each new login for my IdP).

I've switched the IdP to use persistent ID's, so users now have the same ID in each token they provide. This allows Keycloak to not only see a matching email address in the user table, but also a valid IdP link for that specific ID, thus allowing the user to immediately log in without merging, as it now knows the ID's match and the token is for the same user.