bitwarden/server · error · BadRequestException
WebAuthn prf keys must have public-key during rotation.
Error message
WebAuthn prf keys must have public-key during rotation.
What it means
Thrown by WebAuthnLoginKeyRotationValidator when a rotation entry matched a PRF-enabled credential (matched by Id) and has a non-null EncryptedUserKey, but its EncryptedPublicKey is null. The credential needs both the re-encrypted user key and public key to remain valid, so a null public key is rejected.
Source
Thrown at src/Api/KeyManagement/Validators/WebAuthnLoginKeyRotationValidator.cs:51
return result;
}
foreach (var webAuthnCredential in validCredentials)
{
var keyToRotate = keysToRotate.FirstOrDefault(c => c.Id == webAuthnCredential.Id);
if (keyToRotate == null)
{
throw new BadRequestException("All existing webauthn prf keys must be included in the rotation.");
}
if (keyToRotate.EncryptedUserKey == null)
{
throw new BadRequestException("WebAuthn prf keys must have user-key during rotation.");
}
if (keyToRotate.EncryptedPublicKey == null)
{
throw new BadRequestException("WebAuthn prf keys must have public-key during rotation.");
}
result.Add(keyToRotate.ToWebAuthnRotateKeyData());
}
return result;
}
}
View on GitHub (pinned to e93b962371)
Solutions
- Re-encrypt and populate both EncryptedUserKey and EncryptedPublicKey for every PRF-enabled credential.
- Add a pre-send assertion that every key entry has non-null EncryptedUserKey and EncryptedPublicKey.
- If a credential cannot be fully re-encrypted, disable its PRF or remove the passkey before rotating.
Example fix
// before
{ id: c.id, encryptedUserKey: reencrypt(c.userKey) }
// after
{ id: c.id, encryptedUserKey: reencrypt(c.userKey), encryptedPublicKey: reencrypt(c.publicKey) } Defensive patterns
Strategy: validation
Validate before calling
const missingPubKey = payload.keys.filter(k => k.encryptedPublicKey == null);
if (missingPubKey.length) {
throw new Error(`WebAuthn keys missing public key: ${missingPubKey.map(k => k.id).join(', ')}`);
} Type guard
function hasWebAuthnPublicKey(k: { encryptedPublicKey?: string | null }): boolean {
return k.encryptedPublicKey != null && k.encryptedPublicKey.length > 0;
} Try / catch
try {
await api.rotateKey(payload);
} catch (e) {
if (e.status === 400 && /must have public-key/i.test(e.message)) {
payload.keys = payload.keys.map(k => ({ ...k, encryptedPublicKey: k.encryptedPublicKey ?? reencrypt(publicKeyFor(k.id)) }));
return api.rotateKey(payload);
}
throw e;
} Prevention
- Assert every PRF key entry has both non-null EncryptedUserKey and EncryptedPublicKey before submitting.
- Use a single builder that always populates both fields.
- Disable PRF on a credential if its keys cannot be fully re-encrypted.
When it happens
Trigger: The keys array includes the credential Id and EncryptedUserKey but EncryptedPublicKey is null or was omitted during request construction.
Common situations: Client populated the user key but forgot the public key field; a copy/paste or template only carried EncryptedUserKey; serialization dropped the public-key field.
Related errors
- WebAuthn prf keys must have user-key during rotation.
- All existing webauthn prf keys must be included in the rotat
- Rotated encryption keys must be provided for all devices tha
- Emergency access keys cannot be set to null during rotation.
- Reset Password keys cannot be set to null during rotation.
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/0b96364642f7c59d.
Report an issue: GitHub.