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
- Prompt the user to re-enter their master password and resend the request.
- Verify the client's password hashing parameters (iteration count, algorithm, salt) match what the server expects — check the identity token's Kdf parameters.
- If the user recently changed their master password, ensure the client has updated its locally cached hash.
- 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
- Always derive the master password hash using the KDF parameters returned by the server's prelogin response.
- Prompt the user to re-enter their password rather than retrying with the same hash after a failure.
- Keep the client's crypto library version in sync with the server's expectations.
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
- Invalid password.
- The model state is invalid.
- Invalid token
- You cannot remove yourself.
- Organization already belongs to a provider.
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/688a31a57311e98b.
Report an issue: GitHub.