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

  1. Have the user fully touch the YubiKey to emit the complete 44-char OTP and resubmit immediately.
  2. Reject empty or partial values client-side before sending (a valid OTP is 44 chars; a placeholder is 12).
  3. Ensure the YubiKey slot is configured for the Bitwarden credential.
  4. 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

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.