bitwarden/server · error · NotFoundException

Credential not found.

Error message

Credential not found.

What it means

Thrown (HTTP 404) by DELETE /webauthn/{id}/delete when _credentialRepository.GetByIdAsync(id, user.Id) returns null. The lookup is scoped by both credential id and the authenticated user id, so an id that belongs to a different user also yields 404, not 403.

Source

Thrown at src/Api/Auth/Controllers/WebAuthnController.cs:165

        }

        // 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)
        {
            throw new NotFoundException("Credential not found.");
        }

        await _credentialRepository.DeleteAsync(credential);
    }

    private async Task<Core.Entities.User> GetUserAsync()
    {
        var user = await _userService.GetUserByPrincipalAsync(User);
        if (user == null)
        {
            throw new UnauthorizedAccessException();
        }
        return user;
    }

    private async Task<Core.Entities.User> VerifyUserAsync(SecretVerificationRequestModel model)
    {
        var user = await GetUserAsync();

View on GitHub (pinned to e93b962371)

Solutions

  1. Refresh the WebAuthn credential list and retry with a current id.
  2. Treat a 404 on this endpoint as an idempotent success in the UI (the credential is already gone).
  3. Confirm the id originates from the current user's own credential list.

Example fix

// before: surface 404 as an error to the user
catch (ex) { showError('Delete failed'); }

// after: treat 404 as already-deleted success
if (resp.status === 404) { markCredentialRemoved(id); return; }
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: confirm the id is still in the user's credential list before issuing the delete.
const current = await getCredentialList();
if (!current.some(c => c.id === deleteId)) { markRemoved(deleteId); return; }

Try / catch

// Treat 404 from this endpoint as an idempotent success.
try {
  await del(`/webauthn/${id}/delete`, body);
} catch (e) {
  if (e.status === 404) { markCredentialRemoved(id); return; }
  throw e;
}

Prevention

When it happens

Trigger: Client submits a credential id that was already deleted; the id belongs to another user; a stale id from an out-of-sync UI list; a concurrent delete from another session/device between listing and deleting.

Common situations: Credential list not refreshed after a delete on another device; user pasted an old id; race condition between two clients deleting the same passkey.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/a4c38e6b392bab28. Report an issue: GitHub.