bitwarden/server · error · BadRequestException
{name} is invalid.
Error message
{name} is invalid. What it means
BadRequestException with key = name is thrown in ValidateYubiKeyAsync when the supplied YubiKey value is non-empty, not length 12, and fails _userManager.VerifyTwoFactorTokenAsync for the YubiKey provider. A 2-second delay is intentionally applied before throwing to slow brute-force attempts; a 500ms delay applies on the success path for timing consistency.
Source
Thrown at src/Api/Auth/Controllers/TwoFactorController.cs:585
/// <summary>Mints a protected user-verification token bound to <paramref name="user"/> and <paramref name="providerType"/>.</summary>
private string MintProtectedUserVerificationToken(User user, TwoFactorProviderType providerType)
{
var token = _twoFactorUserVerificationTokenableFactory.CreateToken(user, providerType);
return _twoFactorUserVerificationDataProtector.Protect(token);
}
private async Task ValidateYubiKeyAsync(User user, string name, string value)
{
if (string.IsNullOrWhiteSpace(value) || value.Length == 12)
{
return;
}
if (!await _userManager.VerifyTwoFactorTokenAsync(user,
CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey), value))
{
await Task.Delay(2000);
throw new BadRequestException(name, $"{name} is invalid.");
}
await Task.Delay(500);
}
private bool ValidateSsoEmail2FaToken(string ssoEmail2FaSessionToken, User user)
{
return _ssoEmailTwoFactorSessionDataProtector.TryUnprotect(ssoEmail2FaSessionToken, out var decryptedToken) &&
decryptedToken.Valid && decryptedToken.TokenIsValid(user);
}
private async Task ThrowDelayedBadRequestExceptionAsync(string message, int delayTime = 2000)
{
await Task.Delay(delayTime);
throw new BadRequestException(message);
}
}
View on GitHub (pinned to e93b962371)
Solutions
- Have the user fully touch the YubiKey to emit the complete 44-char OTP and resubmit immediately.
- Reject empty or partial values client-side before sending (a valid OTP is 44 chars; a placeholder is 12).
- Ensure the YubiKey slot is configured for the Bitwarden credential.
- Generate a fresh OTP for each request (OTPs are one-time-use).
Example fix
// before
validateYubiKey('ccccccbc') // partial OTP
// after
function isValidYubiKeyOtp(v) { return v.length === 44 || v.length === 12; }
if (!isValidYubiKeyOtp(value)) throw new Error('Invalid YubiKey input');
await validateYubiKey(value); Defensive patterns
Strategy: validation
Validate before calling
function isValidYubiKeyInput(v) { const s = String(v ?? ''); return s.length === 0 || s.length === 12 || s.length === 44; }
if (!isValidYubiKeyInput(value)) throw new Error('Invalid YubiKey input length'); Type guard
function isYubiKeyOtp(v): v is string { return typeof v === 'string' && v.length === 44; } Try / catch
try { await api.put('/users/two-factor/yubikey', model); }
catch (e) {
if (e.response?.status === 400 && /invalid/i.test(JSON.stringify(e.response.data?.error))) {
throw new UserFacingError('YubiKey OTP invalid; touch the key again and resubmit.');
}
throw e;
} Prevention
- Validate OTP length (44) or placeholder (12) before sending.
- Use each OTP only once.
- Expect a 2s server-side delay on failure; avoid tight retry loops.
When it happens
Trigger: PUT /two-factor/yubikey or similar flow calling ValidateYubiKeyAsync where one of multiple YubiKey values (key1/key2/key3) is present but invalid (wrong OTP, partial input, or a non-YubiKey string).
Common situations: User touched the YubiKey only briefly (partial OTP), pasted a truncated string, the slot was configured for a different credential, or the OTP was already used (YubiKey OTPs are single-use server-side).
Related errors
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/8307e3ef74e4aeb9.
Report an issue: GitHub.