-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Description
Before reporting an issue
- I have read and understood the above terms for submitting issues, and I understand that my issue may be closed without action if I do not follow them.
Area
login/ui
Describe the bug
If a user previously had multiple Credentials registered and some of them had duplicated labels, such users can't use these Credentials to authenticate themselves with Keycloak 26.3 anymore.
Version
26.3.2
Regression
- The issue is a regression
Expected behavior
Allow using Credentials with duplicated labels from the past or provide a mechanism to resolve such duplicates.
Actual behavior
Using such a Credential to authenticate causes Exception:
2025-08-18 13:39:14,451 WARN [org.keycloak.services] (executor-thread-20) KC-SERVICES0013: Failed authentication: org.keycloak.models.ModelDuplicateException: Device already exists with the same name
at org.keycloak.models.jpa.JpaUserCredentialStore.validateDuplicateCredential(JpaUserCredentialStore.java:151)
at org.keycloak.models.jpa.JpaUserCredentialStore.updateCredential(JpaUserCredentialStore.java:66)
at org.keycloak.models.jpa.JpaUserProvider.updateCredential(JpaUserProvider.java:891)
at org.keycloak.credential.UserCredentialManager.updateStoredCredential(UserCredentialManager.java:104)
at org.keycloak.models.cache.infinispan.SubjectCredentialManagerCacheAdapter.updateStoredCredential(SubjectCredentialManagerCacheAdapter.java:47)
at org.keycloak.credential.WebAuthnCredentialProvider.isValid(WebAuthnCredentialProvider.java:226)
at org.keycloak.credential.UserCredentialManager.lambda$validate$11(UserCredentialManager.java:274)
at org.keycloak.tracing.NoopTracingProvider.trace(NoopTracingProvider.java:64)
at org.keycloak.tracing.NoopTracingProvider.trace(NoopTracingProvider.java:74)
at org.keycloak.credential.UserCredentialManager.lambda$validate$12(UserCredentialManager.java:273)
at java.base/java.util.Collection.removeIf(Collection.java:576)
at org.keycloak.credential.UserCredentialManager.validate(UserCredentialManager.java:271)
at org.keycloak.credential.UserCredentialManager.lambda$isValid$0(UserCredentialManager.java:77)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
at org.keycloak.credential.UserCredentialManager.isValid(UserCredentialManager.java:77)
at org.keycloak.models.SubjectCredentialManager.isValid(SubjectCredentialManager.java:45)
at org.keycloak.authentication.authenticators.browser.WebAuthnAuthenticator.action(WebAuthnAuthenticator.java:242)
at org.keycloak.authentication.authenticators.browser.WebAuthnPasswordlessAuthenticator.action(WebAuthnPasswordlessAuthenticator.java:115)
at org.keycloak.authentication.DefaultAuthenticationFlow.processAction(DefaultAuthenticationFlow.java:149)
at org.keycloak.authentication.AuthenticationProcessor.authenticationAction(AuthenticationProcessor.java:1064)
at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:378)
at org.keycloak.services.resources.LoginActionsService.processAuthentication(LoginActionsService.java:349)
at org.keycloak.services.resources.LoginActionsService.authenticate(LoginActionsService.java:341)
at org.keycloak.services.resources.LoginActionsService.authenticateForm(LoginActionsService.java:407)
at org.keycloak.services.resources.LoginActionsService$quarkusrestinvoker$authenticateForm_8a5eee1a0ec5f9d46c9be1d4352061fa6806b300.invoke(Unknown Source)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$15.runWith(VertxCoreRecorder.java:638)
at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2675)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2654)
at org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1627)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1594)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:840)
In UI it looks like:
How to Reproduce?
- Setup a user in Keycloak prior to v26.3 like this:
- Upgrade to Keycloak 26.3
- Try to use a Credential with duplicated label at Login
Anything else?
The migration guide for v26.3.0 mentions:
Different credentials of a user need to have different names
When adding an OTP, WebAuthn or any other 2FA credentials, the name the user assigns to this credential needs to be unique for the given user. This allows the user to distinguish between those credentials, and either update or delete them later. If a user tries to create a credential with an already existing name, there is an error message and the user is asked to change the name of the new credential.
But it doesn't mention that even existing labels are affected by this change.