Skip to content

Bug: WebAuthn user_handle is overwritten during Settings flow, breaking existing credentials #4519

@Micaso

Description

@Micaso

Preflight checklist

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

  1. Register a new user using WebAuthn (Passkey) with passwordless: true.
  2. Verify Database: Note the user_handle value in the identity_credentials table for the webauthn method.
  3. Start Settings Flow: Add a second security key via the self-service settings flow.
  4. Verify Database again: Observe that the user_handle has been updated to a new value (likely matching the Identity ID slice), even though it should have remained the same.
  5. Attempt Login: Try to log in using the first key.
  6. Result: The authentication fails because the user_handle in the challenge does not match the one stored in the first authenticator.

Relevant log output

Relevant configuration

selfservice:
  methods:
    webauthn:
      enabled: true
      config: 
        passwordless: true

Version

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not working.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions