-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
Preflight checklist
- I could not find a solution in the existing issues, docs, nor discussions.
- I agree to follow this project's Code of Conduct.
- I have read and am following this repository's Contribution Guidelines.
- I have joined the Ory Community Slack.
- I am signed up to the Ory Security Patch Newsletter.
Ory Network Project
No response
Describe the bug
When adding a second WebAuthn/Passkey via the Settings flow, Ory Kratos (v25.4.0) overwrites the existing user_handle in the identity credentials configuration.
Per the WebAuthn specification, the user.id (user_handle) must be stable for the lifetime of the identity. By overwriting this handle, any authenticators registered prior to the update will fail to authenticate during login. This happens because the handle provided in the login challenge (the new one) no longer matches the handle stored inside the physical security key or passkey provider (the old one).
Potential Root Cause
The issue is located in selfservice/strategy/webauthn/settings.go. The current implementation appears to unconditionally assign the IdentityID to the UserHandle during the settings update:
// Location: selfservice/strategy/webauthn/settings.go
cc.UserHandle = ctxUpdate.Session.IdentityID[:]This logic does not account for the fact that a UserHandle might already be established for that identity's credentials.
Reproducing the bug
- Register a new user using WebAuthn (Passkey) with
passwordless: true. - Verify Database: Note the
user_handlevalue in theidentity_credentialstable for thewebauthnmethod. - Start Settings Flow: Add a second security key via the self-service settings flow.
- Verify Database again: Observe that the
user_handlehas been updated to a new value (likely matching the Identity ID slice), even though it should have remained the same. - Attempt Login: Try to log in using the first key.
- Result: The authentication fails because the
user_handlein the challenge does not match the one stored in the first authenticator.
Relevant log output
Relevant configuration
selfservice:
methods:
webauthn:
enabled: true
config:
passwordless: trueVersion
v25.04
On which operating system are you observing this issue?
None
In which environment are you deploying?
None
Additional Context
Proposed Fix
The UserHandle should only be set if it is currently empty. This ensures that the first-generated handle persists for the life of the identity, allowing all registered keys to remain valid.
if len(cc.UserHandle) == 0 {
cc.UserHandle = ctxUpdate.Session.IdentityID[:]
}or maybe following will do it also right:
cc.UserHandle = webAuthnSess.UserID