bitwarden/server · error · BadRequestException
Unable to update credential.
Error message
Unable to update credential.
What it means
Thrown (HTTP 400) after a WebAuthn assertion succeeds but either no credential was resolved (credential == null) or the resolved authenticator does not report the PRF extension as supported (SupportsPrf != true). The key-rotation flow relies on PRF to derive the new user encryption keys, so a non-PRf passkey is ineligible to update the key set.
Source
Thrown at src/Api/Auth/Controllers/WebAuthnController.cs:146
{
throw new BadRequestException("Passkeys cannot be created for your account. SSO login is required.");
}
}
[Authorize(Policies.Application)]
[HttpPut()]
public async Task UpdateCredential([FromBody] WebAuthnLoginCredentialUpdateRequestModel model)
{
var tokenable = _assertionOptionsDataProtector.Unprotect(model.Token);
if (!tokenable.TokenIsValid(WebAuthnLoginAssertionOptionsScope.UpdateKeySet) || tokenable.Options == null)
{
throw new BadRequestException("The token associated with your request is invalid or has expired. A valid token is required to continue.");
}
var (_, credential) = await _assertWebAuthnLoginCredentialCommand.AssertWebAuthnLoginCredential(tokenable.Options, model.DeviceResponse);
if (credential == null || credential.SupportsPrf != true)
{
throw new BadRequestException("Unable to update credential.");
}
// assign new keys to credential
credential.EncryptedUserKey = model.EncryptedUserKey;
credential.EncryptedPrivateKey = model.EncryptedPrivateKey;
credential.EncryptedPublicKey = model.EncryptedPublicKey;
await _credentialRepository.UpdateAsync(credential);
}
[Authorize(Policies.Web)]
[HttpPost("{id}/delete")]
public async Task Delete(Guid id, [FromBody] SecretVerificationRequestModel model)
{
var user = await VerifyUserAsync(model);
var credential = await _credentialRepository.GetByIdAsync(id, user.Id);
if (credential == null)
{View on GitHub (pinned to e93b962371)
Solutions
- Enroll a new passkey on a PRf-capable authenticator (recent platform authenticator + up-to-date browser) and retry key rotation.
- Confirm the deviceResponse corresponds to a credential id still present for the user (refresh the credential list).
- Fall back to a non-passkey key-rotation path for users without a PRf-capable authenticator.
Defensive patterns
Strategy: type-guard
Validate before calling
// Client-side: before attempting key rotation, confirm the chosen authenticator advertised PRF support during registration/assertion.
Type guard
// Server-side guard you can apply before reaching the throw site: // bool CanRotateViaPrf(WebAuthnCredential c) => c is not null && c.SupportsPrf == true; // Then: if (!CanRotateViaPrf(credential)) return GuidedFallback();
Try / catch
try {
await put('/webauthn', payload);
} catch (e) {
if (e.isBadRequest && /unable to update credential/i.test(e.message)) {
await enrollPrfCapablePasskey(); // re-enroll on a PRf-capable device, then retry
} else { throw e; }
} Prevention
- Surface the SupportsPrf flag in the credential list UI so users know which passkeys can rotate keys.
- Enroll new passkeys on PRf-capable authenticators when key rotation may be needed.
- Provide a non-passkey key-rotation fallback path.
When it happens
Trigger: User's passkey was created on an authenticator that does not expose the PRF extension (older security keys, some platform authenticators); the submitted deviceResponse signs over a credential id that matches no stored record; the credential was deleted between options-generation and assertion.
Common situations: Trying to rotate keys with a passkey enrolled on legacy hardware; browser/authenticator combination that does not surface PRF; mixed authenticator fleet where only some users have PRf-capable devices.
Related errors
- All existing webauthn prf keys must be included in the rotat
- The token associated with your request is invalid or has exp
- WebAuthn prf keys must have user-key during rotation.
- WebAuthn prf keys must have public-key during rotation.
- The token associated with your request is expired. A valid t
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/0f88e846d25ecf56.
Report an issue: GitHub.