bitwarden/server · error · BadRequestException
Unable to delete WebAuthn credential.
Error message
Unable to delete WebAuthn credential.
What it means
BadRequestException 'Unable to delete WebAuthn credential.' is thrown in DELETE /webauthn (DeleteWebAuthn) when model.Id has no value (null/empty guid). The delete command requires a concrete credential id; without it the request is rejected before the command runs.
Source
Thrown at src/Api/Auth/Controllers/TwoFactorController.cs:382
return new TwoFactorWebAuthnUpdateResponseModel(user);
}
[HttpPost("webauthn")]
[Obsolete("This endpoint is deprecated. Use PUT /webauthn instead.")]
public async Task<TwoFactorWebAuthnUpdateResponseModel> PostWebAuthn([FromBody] TwoFactorWebAuthnUpdateRequestModel model)
{
return await PutWebAuthn(model);
}
[HttpDelete("webauthn")]
public async Task<TwoFactorWebAuthnDeleteResponseModel> DeleteWebAuthn(
[FromBody] TwoFactorWebAuthnDeleteRequestModel model)
{
var user = await ValidateUserVerificationTokenAsync(model.UserVerificationToken, TwoFactorProviderType.WebAuthn);
if (!model.Id.HasValue)
{
throw new BadRequestException("Unable to delete WebAuthn credential.");
}
var success = await _deleteTwoFactorWebAuthnCredentialCommand.DeleteTwoFactorWebAuthnCredentialAsync(user, model.Id.Value);
if (!success)
{
throw new BadRequestException("Unable to delete WebAuthn credential.");
}
return new TwoFactorWebAuthnDeleteResponseModel(user);
}
[HttpDelete("webauthn/all")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> DeleteWebAuthnAll(
[FromBody] TwoFactorWebAuthnDeleteAllRequestModel model)
{
var user = await ValidateUserVerificationTokenAsync(model.UserVerificationToken, TwoFactorProviderType.WebAuthn);
await _userService.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn);View on GitHub (pinned to e93b962371)
Solutions
- Include a non-empty Guid in the Id field of the delete request body.
- Verify JSON property casing matches the request model (Id vs id) and that serialization is not dropping nulls.
- Fetch the credential list first and pass the selected credential's Id.
Example fix
// before
api.delete('/users/two-factor/webauthn', { data: { userVerificationToken } }) // missing id
// after
api.delete('/users/two-factor/webauthn', { data: { userVerificationToken, id: selectedCredentialId } }); Defensive patterns
Strategy: validation
Validate before calling
if (!model.id) throw new Error('A WebAuthn credential id is required to delete'); Type guard
function hasCredentialId(m): m is { id: string } { return !!m?.id && typeof m.id === 'string'; } Try / catch
try { await api.delete('/users/two-factor/webauthn', { data: model }); }
catch (e) {
if (e.response?.status === 400 && /delete WebAuthn/.test(e.response.data?.message) && !model.id) {
throw new UserFacingError('Select a credential to delete.');
}
throw e;
} Prevention
- Always include the credential Id in the delete body.
- Verify JSON casing (Id/id) matches the server model.
- Fetch the credential list first to obtain a valid Id.
When it happens
Trigger: DELETE /api/users/two-factor/webauthn (TwoFactorController line 382) where the request body omits Id or sends it as null. The !model.Id.HasValue guard fires.
Common situations: Client serializes the credential id as null, sends an empty body, or references a credential list that has no selected id. A deserialization mismatch (wrong casing) can also leave Id null.
Related errors
- Unable to complete WebAuthn registration.
- Credential not found.
- All existing webauthn prf keys must be included in the rotat
- Invalid owner. Owner must be an existing Bitwarden user.
- Failed to remove organization vault. Please contact support.
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/4d9f5a84161da3a2.
Report an issue: GitHub.