bitwarden/server · warning · BadRequestException
Please provide an email and device identifier
Error message
Please provide an email and device identifier
What it means
Thrown by GET /knowndevice/{email}/{identifier} (and the header-based GetByIdentifierQuery) when either the email or device identifier resolves to null, empty, or whitespace. The route exists to let a client check whether a device is already known before login; it refuses to query the repository with a blank argument. The path form is deprecated because URL encoding corrupts emails, so callers should use the X-Request-Email / X-Device-Identifier header form.
Source
Thrown at src/Api/Controllers/DevicesController.cs:297
{
await Deactivate(id);
}
[AllowAnonymous]
[HttpGet("knowndevice")]
public async Task<bool> GetByIdentifierQuery(
[Required][FromHeader(Name = "X-Request-Email")] string Email,
[Required][FromHeader(Name = "X-Device-Identifier")] string DeviceIdentifier)
=> await GetByEmailAndIdentifier(CoreHelpers.Base64UrlDecodeString(Email), DeviceIdentifier);
[Obsolete("Path is deprecated due to encoding issues, use /knowndevice instead.")]
[AllowAnonymous]
[HttpGet("knowndevice/{email}/{identifier}")]
public async Task<bool> GetByEmailAndIdentifier(string email, string identifier)
{
if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(identifier))
{
throw new BadRequestException("Please provide an email and device identifier");
}
var user = await _userRepository.GetByEmailAsync(email);
if (user == null)
{
return false;
}
var device = await _deviceRepository.GetByIdentifierAsync(identifier, user.Id);
return device != null;
}
[HttpPost("lost-trust")]
public void PostLostTrust()
{
var userId = _currentContext.UserId.GetValueOrDefault();
if (userId == default)
{View on GitHub (pinned to e93b962371)
Solutions
- Send both a non-empty, non-whitespace email (base64url-encoded for X-Request-Email) and a non-empty device identifier.
- Migrate off the deprecated /knowndevice/{email}/{identifier} path form to the header-based endpoint to avoid email-encoding corruption.
- Base64url-encode the email client-side before placing it in X-Request-Email (the controller calls CoreHelpers.Base64UrlDecodeString on it).
- Trim and validate both values on the client before issuing the request.
Example fix
// before: header value is whitespace
// X-Request-Email: " "
// X-Device-Identifier: " "
//
// after: base64url-encode a real email, supply real identifier
var emailB64 = base64urlEncode("user@example.com"); // e.g. "dXNlckBleGFtcGxlLmNvbQ"
client.get("/devices/knowndevice", {
headers: { "X-Request-Email": emailB64, "X-Device-Identifier": deviceId }
}); Defensive patterns
Strategy: validation
Validate before calling
// Validate before calling /devices/knowndevice
function canQueryKnownDevice(email, identifier) {
if (!email || !email.trim()) return false;
if (!identifier || !identifier.trim()) return false;
try { base64urlDecode(email); } catch { return false; } // header form expects base64url
return true;
}
if (canQueryKnownDevice(email, deviceId)) {
await client.getByIdentifierQuery(email, deviceId);
} Type guard
function isNonEmptyString(v) { return typeof v === 'string' && v.trim().length > 0; } Prevention
- Trim and non-empty-check both fields on the client before issuing the request.
- Use the header-based endpoint (X-Request-Email base64url-encoded) instead of the deprecated path form.
- Reject pure-whitespace values client-side since [Required] alone does not.
When it happens
Trigger: Hitting GET /knowndevice with a whitespace-only email or identifier segment, or calling the header form with X-Request-Email / X-Device-Identifier set to spaces. The [Required] attribute rejects null and empty string but lets pure-whitespace values through, so only the IsNullOrWhiteSpace guard inside catches them. Also fires if a client base64url-decodes to an empty string.
Common situations: Clients migrating off the deprecated path form and sending raw (non-base64url) headers by mistake; device-provisioning scripts that send a placeholder ' ' while bootstrapping; URL-encoded emails that decode to empty.
Related errors
- Route parameter 'orgId' or 'organizationId' is missing or in
- Route parameter 'orgId' or 'organizationId' is missing or in
- Route parameter '{attr.OrganizationUserIdRouteParam}' is mis
- Requested collections must belong to the same organization.
- ExternalId cannot exceed 300 characters.
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/620761468795fed0.
Report an issue: GitHub.