bitwarden/server · error · BadRequestException

User verification failed.

Error message

User verification failed.

What it means

Thrown as BadRequestException(string.Empty, "User verification failed.") (HTTP 400) from POST /accounts/api-key. _userService.VerifySecretAsync(user, model.Secret) returns false — the provided master password hash does not match the stored value. A 2-second Task.Delay precedes the throw as a timing-attack mitigation to make brute-force enumeration slower.

Source

Thrown at src/Api/Auth/Controllers/AccountsController.cs:679

        var user = await _userService.GetUserByPrincipalAsync(User);
        var token = await _userService.GenerateSignInTokenAsync(user, TokenPurposes.LinkSso);
        var userIdentifier = $"{user.Id},{token}";
        return userIdentifier;
    }

    [HttpPost("api-key")]
    public async Task<ApiKeyResponseModel> ApiKey([FromBody] SecretVerificationRequestModel model)
    {
        var user = await _userService.GetUserByPrincipalAsync(User);
        if (user == null)
        {
            throw new UnauthorizedAccessException();
        }

        if (!await _userService.VerifySecretAsync(user, model.Secret))
        {
            await Task.Delay(2000);
            throw new BadRequestException(string.Empty, "User verification failed.");
        }

        return new ApiKeyResponseModel(user);
    }

    [HttpPost("rotate-api-key")]
    public async Task<ApiKeyResponseModel> RotateApiKey([FromBody] SecretVerificationRequestModel model)
    {
        var user = await _userService.GetUserByPrincipalAsync(User);
        if (user == null)
        {
            throw new UnauthorizedAccessException();
        }

        if (!await _userService.VerifySecretAsync(user, model.Secret))
        {
            await Task.Delay(2000);
            throw new BadRequestException(string.Empty, "User verification failed.");

View on GitHub (pinned to e93b962371)

Solutions

  1. Prompt the user to re-enter their master password and resend the request.
  2. Verify the client's password hashing parameters (iteration count, algorithm, salt) match what the server expects — check the identity token's Kdf parameters.
  3. If the user recently changed their master password, ensure the client has updated its locally cached hash.
  4. Check for client version mismatches where the hashing algorithm (PBKDF2 vs Argon2id) or iteration count differs from server expectations.

Example fix

// before: sending a stale or incorrect password hash
var resp = await client.PostAsJsonAsync("/accounts/api-key",
    new SecretVerificationRequestModel { Secret = oldMasterPasswordHash });
// 400: User verification failed.

// after: re-derive hash from freshly entered password using correct params
var hash = Crypto.HashPassword(promptedPassword, kdfParams);
var resp = await client.PostAsJsonAsync("/accounts/api-key",
    new SecretVerificationRequestModel { Secret = hash });
Defensive patterns

Strategy: validation

Validate before calling

// Verify the master password hash is correctly derived before sending
var prelogin = await GetPreloginInfoAsync(email);
var derivedHash = PBKDF2.Sha256(masterPassword, prelogin.Email, prelogin.Iterations);

// Optionally: validate the hash length/format matches expectations
if (derivedHash.Length != expectedHashLength) {
    return Error("Password hash derivation produced an unexpected result");
}

Try / catch

try {
    var resp = await client.PostAsJsonAsync("/accounts/api-key",
        new SecretVerificationRequestModel { Secret = derivedHash });
    resp.EnsureSuccessStatusCode();
} catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.BadRequest) {
    // Secret verification failed — prompt user to re-enter master password
    ShowUserError("Master password verification failed. Please re-enter your master password.");
}

Prevention

When it happens

Trigger: POST /accounts/api-key is called with a SecretVerificationRequestModel whose Secret field (the master password hash) is incorrect. The user exists and is authenticated, but the secret verification fails.

Common situations: User recently changed their master password but the client is still sending the old hash. The client-side PBKDF2/argon2 iteration count or algorithm was updated but the client hasn't been refreshed. The user mistyped their master password. A different hashing implementation on the client produces a hash that doesn't match the server's stored value.

Related errors


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