{"record":{"id":"548b587c1c7468a4","repo":"bitwarden/server","slug":"ssoorganizationidmismatch","errorCode":null,"errorMessage":"SsoOrganizationIdMismatch","messagePattern":"SsoOrganizationIdMismatch","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"bitwarden_license/src/Sso/Controllers/AccountController.cs","lineNumber":260,"sourceCode":"    /// <param name=\"scheme\">The authentication scheme (organization ID) to validate.</param>\n    /// <param name=\"ssoToken\">The SSO token to validate against.</param>\n    /// <exception cref=\"Exception\">Thrown if the scheme (organization ID) does not match the organization ID found in the ssoToken.</exception>\n    private void ValidateSchemeAgainstSsoToken(string scheme, string ssoToken)\n    {\n        SsoTokenable tokenable;\n\n        try\n        {\n            tokenable = _dataProtector.Unprotect(ssoToken);\n        }\n        catch\n        {\n            throw new Exception(_i18nService.T(\"InvalidSsoToken\"));\n        }\n\n        if (!Guid.TryParse(scheme, out var schemeOrgId) || tokenable.OrganizationId != schemeOrgId)\n        {\n            throw new Exception(_i18nService.T(\"SsoOrganizationIdMismatch\"));\n        }\n    }\n\n    [HttpGet]\n    public async Task<IActionResult> ExternalCallback()\n    {\n        // Read external identity from the temporary cookie\n        var result = await HttpContext.AuthenticateAsync(\n            AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);\n\n        if (!result.Succeeded)\n        {\n            throw new Exception(_i18nService.T(\"ExternalAuthenticationError\"));\n        }\n\n        // See if the user has logged in with this SSO provider before and has already been provisioned.\n        // This is signified by the user existing in the User table and the SSOUser table for the SSO provider they're using.\n        var (possibleSsoLinkedUser, provider, providerUserId, claims, ssoConfigData) = await FindUserFromExternalProviderAsync(result);","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Sso/Controllers/AccountController.cs#L242-L278","documentation":"Thrown in AccountController.ValidateSchemeAgainstSsoToken (line 260) when the scheme parameter is not a valid GUID or when the GUID it parses to does not match the OrganizationId embedded in the unprotected SSO token. This prevents a user from authenticating against one org's IdP while using another org's token.","triggerScenarios":"ExternalChallenge receives a scheme that is not a GUID-parseable string, or whose parsed GUID differs from tokenable.OrganizationId in the unprotected token.","commonSituations":"scheme query param was manually changed or spoofed; client passed the wrong organization ID; token was minted for org A but the request targets org B; URL parameters got reordered or corrupted.","solutions":["Ensure the scheme passed to ExternalChallenge is exactly the organization ID that was used when the SSO token was minted in PreValidate.","Do not modify or reuse the scheme parameter between token creation and challenge.","Validate client-side that scheme is a valid GUID before navigating to ExternalChallenge."],"exampleFix":"// before — mismatched scheme and token origin\nRedirectToAction(\"ExternalChallenge\", new { scheme = wrongOrgId, ssoToken });\n// after — scheme comes from the same org as the token\nvar org = await _organizationRepository.GetByIdentifierAsync(domainHint);\nRedirectToAction(\"ExternalChallenge\", new { scheme = org.Id.ToString(), ssoToken });","handlingStrategy":"validation","validationCode":"// Ensure scheme matches the token's org before challenge\nif (!Guid.TryParse(scheme, out var schemeOrgId))\n    return BadRequest(\"Invalid scheme format.\");\nvar tokenable = _dataProtector.Unprotect(ssoToken);\nif (tokenable.OrganizationId != schemeOrgId)\n    return BadRequest(\"Scheme does not match SSO token organization.\");","typeGuard":null,"tryCatchPattern":"try { ValidateSchemeAgainstSsoToken(scheme, ssoToken); }\ncatch (Exception ex) when (ex.Message.Contains(\"SsoOrganizationIdMismatch\"))\n{ /* log security event; restart flow with correct scheme */ }","preventionTips":["Derive the scheme from the same organization used to mint the SSO token.","Treat scheme/token mismatches as potential tampering — log and alert.","Do not allow client-side override of the scheme parameter."],"tags":["sso","authentication","organization","token","security","mismatch"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}