{"record":{"id":"620761468795fed0","repo":"bitwarden/server","slug":"please-provide-an-email-and-device-identifier","errorCode":null,"errorMessage":"Please provide an email and device identifier","messagePattern":"Please provide an email and device identifier","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"src/Api/Controllers/DevicesController.cs","lineNumber":297,"sourceCode":"    {\n        await Deactivate(id);\n    }\n\n    [AllowAnonymous]\n    [HttpGet(\"knowndevice\")]\n    public async Task<bool> GetByIdentifierQuery(\n            [Required][FromHeader(Name = \"X-Request-Email\")] string Email,\n            [Required][FromHeader(Name = \"X-Device-Identifier\")] string DeviceIdentifier)\n        => await GetByEmailAndIdentifier(CoreHelpers.Base64UrlDecodeString(Email), DeviceIdentifier);\n\n    [Obsolete(\"Path is deprecated due to encoding issues, use /knowndevice instead.\")]\n    [AllowAnonymous]\n    [HttpGet(\"knowndevice/{email}/{identifier}\")]\n    public async Task<bool> GetByEmailAndIdentifier(string email, string identifier)\n    {\n        if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(identifier))\n        {\n            throw new BadRequestException(\"Please provide an email and device identifier\");\n        }\n\n        var user = await _userRepository.GetByEmailAsync(email);\n        if (user == null)\n        {\n            return false;\n        }\n\n        var device = await _deviceRepository.GetByIdentifierAsync(identifier, user.Id);\n        return device != null;\n    }\n\n    [HttpPost(\"lost-trust\")]\n    public void PostLostTrust()\n    {\n        var userId = _currentContext.UserId.GetValueOrDefault();\n        if (userId == default)\n        {","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Controllers/DevicesController.cs#L279-L315","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: header value is whitespace\n//   X-Request-Email: \"   \"\n//   X-Device-Identifier: \"  \"\n//\n// after: base64url-encode a real email, supply real identifier\nvar emailB64 = base64urlEncode(\"user@example.com\"); // e.g. \"dXNlckBleGFtcGxlLmNvbQ\"\nclient.get(\"/devices/knowndevice\", {\n  headers: { \"X-Request-Email\": emailB64, \"X-Device-Identifier\": deviceId }\n});","handlingStrategy":"validation","validationCode":"// Validate before calling /devices/knowndevice\nfunction canQueryKnownDevice(email, identifier) {\n  if (!email || !email.trim()) return false;\n  if (!identifier || !identifier.trim()) return false;\n  try { base64urlDecode(email); } catch { return false; } // header form expects base64url\n  return true;\n}\nif (canQueryKnownDevice(email, deviceId)) {\n  await client.getByIdentifierQuery(email, deviceId);\n}","typeGuard":"function isNonEmptyString(v) { return typeof v === 'string' && v.trim().length > 0; }","tryCatchPattern":null,"preventionTips":["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."],"tags":["validation","devices","api","deprecated-route","http-400"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}