bitwarden/server · error · Exception

ExternalAuthenticationError

Error message

ExternalAuthenticationError

What it means

Thrown in AccountController.ExternalCallback (line 273) when HttpContext.AuthenticateAsync against the BitwardenExternalCookieAuthenticationScheme does not return a successful result. The temporary external cookie is set during the Challenge redirect and must be present and valid when the IdP redirects back.

Source

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

            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);

        // We will look these up as required (lazy resolution) to avoid multiple DB hits.
        Organization? organization = null;
        OrganizationUser? orgUser = null;

        // The user has not authenticated with this SSO provider before.
        // They could have an existing Bitwarden account in the User table though.
        if (possibleSsoLinkedUser == null)
        {
            // FIXME: Update this file to be null safe and then delete the line below
#nullable disable
            // If we're manually linking to SSO, the user's external identifier will be passed as query string parameter.
            var userIdentifier = result.Properties.Items.Keys.Contains("user_identifier")

View on GitHub (pinned to e93b962371)

Solutions

  1. Ensure the user navigates through the full Challenge → IdP → Callback flow; do not bookmark or directly visit the ExternalCallback URL.
  2. Check browser cookie settings and SameSite configuration for the external auth cookie.
  3. Verify the IdP redirect URI matches the configured callback exactly.
  4. Inspect the IdP's response for errors (check IdP logs and browser network tab).
Defensive patterns

Strategy: try-catch

Try / catch

try { await ExternalCallback(); }
catch (Exception ex) when (ex.Message.Contains("ExternalAuthenticationError"))
{
    _logger.LogError(ex, "External auth failed during SSO callback");
    return Redirect($"/login?error=sso_external_error");
}

Prevention

When it happens

Trigger: The external authentication cookie is missing, expired, corrupted, or the IdP returned an error that prevented a valid cookie from being set.

Common situations: Cookie blocked by browser privacy settings; user took too long and the temp cookie expired; load balancer or proxy stripping cookies; IdP returned an error response instead of a valid auth code; the callback URL was accessed directly without going through the Challenge flow.

Understand the failure class

Related errors


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