{"record":{"id":"9e825b61a4230533","repo":"bitwarden/server","slug":"invaliduseridentifier","errorCode":null,"errorMessage":"InvalidUserIdentifier","messagePattern":"InvalidUserIdentifier","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"bitwarden_license/src/Sso/Controllers/AccountController.cs","lineNumber":789,"sourceCode":"                    OrganizationUserStatusType.Invited,\n                    OrganizationUserStatusType.Accepted,\n                    OrganizationUserStatusType.Confirmed,\n                ],\n                organization.DisplayName());\n        }\n        else\n        {\n            throw new Exception(_i18nService.T(\"CouldNotFindOrganizationUser\", user.Id, organization.Id));\n        }\n    }\n\n    private async Task<User?> GetUserFromManualLinkingDataAsync(string userIdentifier)\n    {\n        User? user = null;\n        var split = userIdentifier.Split(\",\");\n        if (split.Length < 2)\n        {\n            throw new Exception(_i18nService.T(\"InvalidUserIdentifier\"));\n        }\n\n        var userId = split[0];\n        var token = split[1];\n\n        var tokenOptions = new TokenOptions();\n\n        var claimedUser = await _userService.GetUserByIdAsync(userId);\n        if (claimedUser != null)\n        {\n            var tokenIsValid = await _userManager.VerifyUserTokenAsync(\n                claimedUser, tokenOptions.PasswordResetTokenProvider, TokenPurposes.LinkSso, token);\n            if (tokenIsValid)\n            {\n                user = claimedUser;\n            }\n            else\n            {","sourceCodeStart":771,"sourceCodeEnd":807,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Sso/Controllers/AccountController.cs#L771-L807","documentation":"Thrown during SSO login when the manual user-linking identifier string does not contain at least two comma-separated segments. The identifier is expected in the format 'userId,token' and is split on ','; if fewer than two parts result, the format is considered invalid. This path is reached only when a userIdentifier value is present in the SSO callback (manual linking flow) at AccountController.cs:549.","triggerScenarios":"An SSO authentication callback carries a userIdentifier that is empty, has no comma, or is a single token. This happens when the linking URL/state is truncated, manually edited, or generated by a client that omits the token portion.","commonSituations":"A custom or older client constructs the linking payload without the LinkSso token. The user copies/pastes a partial linking URL. A bug in the calling code serializes only the userId without the comma-delimited token.","solutions":["Ensure the userIdentifier passed to the SSO flow is always formatted as 'userId,token' with both segments non-empty.","Audit the client or middleware that builds the manual linking data to confirm it appends the LinkSso token after the comma.","If the token is missing, regenerate the SSO linking invitation from the organization admin console so a fresh token is issued."],"exampleFix":"// before\nvar userIdentifier = userId.ToString();\n// after\nvar token = await userManager.GenerateUserTokenAsync(user, tokenOptions.PasswordResetTokenProvider, TokenPurposes.LinkSso);\nvar userIdentifier = $\"{user.Id},{token}\";","handlingStrategy":"validation","validationCode":"// Validate userIdentifier format before calling the SSO linking API\nif (string.IsNullOrWhiteSpace(userIdentifier) ||\n    userIdentifier.Split(',').Length < 2 ||\n    string.IsNullOrWhiteSpace(userIdentifier.Split(',')[0]) ||\n    string.IsNullOrWhiteSpace(userIdentifier.Split(',')[1]))\n{\n    return BadRequest(\"userIdentifier must be in 'userId,token' format with both parts non-empty.\");\n}","typeGuard":"// C# has no runtime type guard for strings; use a validation method\nstatic bool IsValidUserIdentifier(string identifier)\n    => !string.IsNullOrWhiteSpace(identifier)\n       && identifier.Split(',').Length >= 2\n       && identifier.Split(',').All(p => !string.IsNullOrWhiteSpace(p));","tryCatchPattern":"try { var user = await GetUserFromManualLinkingDataAsync(userIdentifier); }\ncatch (Exception ex) when (ex.Message.Contains(\"InvalidUserIdentifier\"))\n{ /* Show user-friendly 'linking data is malformed' message, prompt re-invitation */ }","preventionTips":["Always construct the userIdentifier as $\"{userId},{token}\" with both values validated non-empty.","Add a unit test that asserts GetUserFromManualLinkingDataAsync rejects malformed identifiers.","Log the userIdentifier length (not value) when this fires to diagnose truncation."],"tags":["sso","authentication","validation","bitwarden-license"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}