bitwarden/server · error · BadRequestException
Invalid password.
Error message
Invalid password.
What it means
Thrown as a 400 BadRequestException("MasterPasswordHash", "Invalid password.") from the organization API-key retrieval endpoint. After resolving the user and the organization API key, the caller's secret (model.Secret) is verified via _userService.VerifySecretAsync; on failure a 2-second delay runs and the error is keyed under MasterPasswordHash. This guards retrieval of an organization API key (e.g., SCIM key) behind master-password re-verification.
Source
Thrown at src/Api/AdminConsole/Controllers/OrganizationsController.cs:406
{
throw new NotFoundException();
}
}
var organizationApiKey = await _getOrganizationApiKeyQuery
.GetOrganizationApiKeyAsync(organization.Id, model.Type) ??
await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type);
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("MasterPasswordHash", "Invalid password.");
}
var response = new ApiKeyResponseModel(organizationApiKey);
return response;
}
[HttpGet("{id}/api-key-information/{type?}")]
public async Task<ListResponseModel<OrganizationApiKeyInformation>> ApiKeyInformation(Guid id,
[FromRoute] OrganizationApiKeyType? type)
{
if (!await HasApiKeyAccessAsync(id, type))
{
throw new NotFoundException();
}
var apiKeys = await _organizationApiKeyRepository.GetManyByOrganizationIdTypeAsync(id, type);
return new ListResponseModel<OrganizationApiKeyInformation>(View on GitHub (pinned to e93b962371)
Solutions
- Re-derive MasterPasswordHash with the user's current KDF configuration and resubmit model.Secret.
- If the password was recently changed, sign out and back in so the client recomputes the hash.
- Confirm the field sent is the secret the endpoint expects (Secret) and that it is the derived hash, not plaintext.
- Verify the authenticated principal is the account whose secret is being checked.
Example fix
// before
body.Secret = oldHash;
// after
body.Secret = await crypto.hashPassword(masterPassword, user.kdf);
await api.post(`organizations/${id}/api-key`, body); Defensive patterns
Strategy: try-catch
Validate before calling
function validSecret(secret) {
return typeof secret === 'string' && 0 < secret.length && secret.length < 1024;
} Type guard
function isSecretModel(v): v is { secret: string } {
return !!v && typeof v.secret === 'string' && v.secret.length > 0;
} Try / catch
try {
await api.post(`organizations/${id}/api-key`, body);
} catch (e) {
if (e?.response?.status === 400 && e.response.data?.ValidationErrors?.['MasterPasswordHash']?.some(m => /Invalid password/i.test(m))) {
promptForMasterPasswordAgain();
} else throw e;
} Prevention
- Derive the master-password hash with the current KDF before sending.
- Re-authenticate after a password or KDF change.
- Send the derived hash, never plaintext.
When it happens
Trigger: POST to get/create an organization API key with a valid session and existing API key record, but model.Secret does not verify against the current user. Occurs when the admin's stored master-password hash differs from the supplied hash.
Common situations: Admin entered the wrong master password in the 'verify to view API key' prompt; client derived the hash with outdated KDF iterations after a server-side KDF change; password was changed elsewhere and the local session still holds the old derived hash.
Related errors
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/2be3802f3a196d33.
Report an issue: GitHub.