bitwarden/server · error · BadRequestException

Unable to complete WebAuthn registration.

Error message

Unable to complete WebAuthn registration.

What it means

BadRequestException 'Unable to complete WebAuthn registration.' is thrown in POST /webauthn (WebAuthnController.Post) when _createWebAuthnLoginCredentialCommand.CreateWebAuthnLoginCredentialAsync returns null. The command performs fido2 attestation verification and persistence; null means attestation failed, the device response did not match the options, or the credential could not be saved.

Source

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

    }

    [Authorize(Policies.Application)]
    [HttpPost("")]
    public async Task<WebAuthnCredentialResponseModel> Post([FromBody] WebAuthnLoginCredentialCreateRequestModel model)
    {
        var user = await GetUserAsync();
        await ValidateIfUserCanUsePasskeyLogin(user.Id);
        var tokenable = _createOptionsDataProtector.Unprotect(model.Token);

        if (!tokenable.TokenIsValid(user))
        {
            throw new BadRequestException("The token associated with your request is expired. A valid token is required to continue.");
        }

        var credential = await _createWebAuthnLoginCredentialCommand.CreateWebAuthnLoginCredentialAsync(user, model.Name, tokenable.Options, model.DeviceResponse, model.SupportsPrf, model.EncryptedUserKey, model.EncryptedPublicKey, model.EncryptedPrivateKey);
        if (credential == null)
        {
            throw new BadRequestException("Unable to complete WebAuthn registration.");
        }

        return new WebAuthnCredentialResponseModel(credential);
    }

    private async Task ValidateIfUserCanUsePasskeyLogin(Guid userId)
    {
        var requireSsoPolicyRequirement = await _policyRequirementQuery.GetAsyncVNext<RequireSsoPolicyRequirement>(userId);

        if (!requireSsoPolicyRequirement.CanUsePasskeyLogin)
        {
            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)

View on GitHub (pinned to e93b962371)

Solutions

  1. Regenerate creation options and complete the ceremony in one flow without delay.
  2. Verify the calling origin matches the server's fido2 RP id configuration.
  3. Ensure EncryptedUserKey/EncryptedPublicKey/EncryptedPrivateKey are correctly encrypted for the user.
  4. Avoid registering the same passkey twice; delete the old one first if needed.

Example fix

// before
api.post('/webauthn', { token, name, deviceResponse, supportsPrf: false, encryptedUserKey: null })
// after
const encryptedUserKey = await cryptoService.encrypt(userKey);
api.post('/webauthn', { token, name, deviceResponse, supportsPrf, encryptedUserKey, encryptedPublicKey, encryptedPrivateKey });
Defensive patterns

Strategy: validation

Validate before calling

if (!model.token || !model.deviceResponse) throw new Error('Token and deviceResponse are required');
if (model.supportsPrf && !model.encryptedUserKey) throw new Error('EncryptedUserKey required with PRF');

Type guard

function isWebAuthnCreateModel(m): m is WebAuthnCreateModel { return !!m?.token && !!m?.deviceResponse && typeof m.name === 'string'; }

Try / catch

try { await api.post('/webauthn', model); }
catch (e) {
  if (e.response?.status === 400 && /WebAuthn registration/.test(e.response.data?.message)) {
    throw new RetryableError('Restart the WebAuthn flow with fresh options');
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /webauthn (passkey creation, line 117) where model.DeviceResponse fails fido2 attestation/challenge verification against tokenable.Options, or the credential already exists (duplicate credential id).

Common situations: Device response generated against different options/origin/RP id, unsupported authenticator, expired challenge, duplicate registration of the same credential, or PRF/encrypted-key payload issues (model.EncryptedUserKey/EncryptedPublicKey/EncryptedPrivateKey malformed).

Related errors


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