bitwarden/server · error · Exception

SsoOrganizationIdMismatch

Error message

SsoOrganizationIdMismatch

What it means

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.

Source

Thrown at bitwarden_license/src/Sso/Controllers/AccountController.cs:260

    /// <param name="scheme">The authentication scheme (organization ID) to validate.</param>
    /// <param name="ssoToken">The SSO token to validate against.</param>
    /// <exception cref="Exception">Thrown if the scheme (organization ID) does not match the organization ID found in the ssoToken.</exception>
    private void ValidateSchemeAgainstSsoToken(string scheme, string ssoToken)
    {
        SsoTokenable tokenable;

        try
        {
            tokenable = _dataProtector.Unprotect(ssoToken);
        }
        catch
        {
            throw new Exception(_i18nService.T("InvalidSsoToken"));
        }

        if (!Guid.TryParse(scheme, out var schemeOrgId) || tokenable.OrganizationId != schemeOrgId)
        {
            throw new Exception(_i18nService.T("SsoOrganizationIdMismatch"));
        }
    }

    [HttpGet]
    public async Task<IActionResult> ExternalCallback()
    {
        // Read external identity from the temporary cookie
        var result = await HttpContext.AuthenticateAsync(
            AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme);

        if (!result.Succeeded)
        {
            throw new Exception(_i18nService.T("ExternalAuthenticationError"));
        }

        // See if the user has logged in with this SSO provider before and has already been provisioned.
        // This is signified by the user existing in the User table and the SSOUser table for the SSO provider they're using.
        var (possibleSsoLinkedUser, provider, providerUserId, claims, ssoConfigData) = await FindUserFromExternalProviderAsync(result);

View on GitHub (pinned to e93b962371)

Solutions

  1. Ensure the scheme passed to ExternalChallenge is exactly the organization ID that was used when the SSO token was minted in PreValidate.
  2. Do not modify or reuse the scheme parameter between token creation and challenge.
  3. Validate client-side that scheme is a valid GUID before navigating to ExternalChallenge.

Example fix

// before — mismatched scheme and token origin
RedirectToAction("ExternalChallenge", new { scheme = wrongOrgId, ssoToken });
// after — scheme comes from the same org as the token
var org = await _organizationRepository.GetByIdentifierAsync(domainHint);
RedirectToAction("ExternalChallenge", new { scheme = org.Id.ToString(), ssoToken });
Defensive patterns

Strategy: validation

Validate before calling

// Ensure scheme matches the token's org before challenge
if (!Guid.TryParse(scheme, out var schemeOrgId))
    return BadRequest("Invalid scheme format.");
var tokenable = _dataProtector.Unprotect(ssoToken);
if (tokenable.OrganizationId != schemeOrgId)
    return BadRequest("Scheme does not match SSO token organization.");

Try / catch

try { ValidateSchemeAgainstSsoToken(scheme, ssoToken); }
catch (Exception ex) when (ex.Message.Contains("SsoOrganizationIdMismatch"))
{ /* log security event; restart flow with correct scheme */ }

Prevention

When it happens

Trigger: ExternalChallenge receives a scheme that is not a GUID-parseable string, or whose parsed GUID differs from tokenable.OrganizationId in the unprotected token.

Common situations: 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.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/548b587c1c7468a4. Report an issue: GitHub.