elsa-workflows/elsa-core · error · OpenIdConnectAuthenticationException

The identity provider callback issuer did not match the…

Error message

The identity provider callback issuer did not match the initiated request.

What it means

When the adapter state (from the protected transaction payload) exists, AuthenticateCallbackAsync compares adapterState.Issuer with the issuer from the resolved provider metadata using an ordinal comparison. A mismatch means the discovery document at callback time declares a different issuer than the one recorded when the flow started, so the callback may be from a different or spoofed provider.

Solutions

  1. Confirm the connection's DiscoveryUrl/Issuer settings were not edited between initiation and callback; restart the login flow after any change.
  2. Check the provider's discovery document issuer matches the configured issuer exactly (case and trailing slash matter — comparison is ordinal).
  3. In multi-tenant/realm setups, make sure the realm-specific discovery URL is used consistently for the whole flow.
Defensive patterns

Strategy: validation

Validate before calling

var meta = await ResolveMetadataAsync(settings); if (!string.Equals(savedIssuer, meta.Issuer, StringComparison.Ordinal)) logger.LogError("Issuer drift: {Saved} vs {Current}", savedIssuer, meta.Issuer);

Try / catch

try { await adapter.AuthenticateCallbackAsync(context); } catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains("issuer")) { logger.LogError(ex, "OIDC issuer mismatch"); throw; }

Prevention

When it happens

Trigger: AuthenticateCallbackAsync where ReadAdapterState returns a non-null state whose Issuer differs from metadata.Issuer resolved via ResolveMetadataAsync.

Common situations: Discovery URL changed to a different provider or realm mid-flow; the provider's metadata issuer changed (e.g. trailing slash differences or realm rename); load balancer routes callback to an environment configured for another tenant.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/a58e9d53d8607617. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs:91

            ["code_challenge_method"] = "S256"
        };

        return new(WithQuery(metadata.AuthorizationEndpoint, query), state);
    }

    public async ValueTask<ExternalAuthenticationResult> AuthenticateCallbackAsync(ExternalCallbackContext context, CancellationToken cancellationToken = default)
    {
        if (TryGetParameter(context.Parameters, "error", out _))
            throw new OpenIdConnectAuthenticationException("The identity provider rejected the authentication request.");

        if (!TryGetParameter(context.Parameters, "state", out var state) || !FixedTimeEquals(state, context.CorrelationState))
            throw new OpenIdConnectAuthenticationException("The identity provider callback could not be correlated.");

        var settings = await GetSettingsAsync(context.Connection.Connection.AdapterSettings, cancellationToken);
        var metadata = await ResolveMetadataAsync(settings, cancellationToken);
        var adapterState = ReadAdapterState(context.Transaction.ProtectedPayload);
        if (adapterState is not null && !string.Equals(adapterState.Issuer, metadata.Issuer, StringComparison.Ordinal))
            throw new OpenIdConnectAuthenticationException("The identity provider callback issuer did not match the initiated request.");

        var idToken = await ExchangeCodeAsync(settings, metadata, context, adapterState?.CodeVerifier, cancellationToken);
        var principal = await ValidateIdTokenAsync(idToken, settings, metadata, cancellationToken);
        var nonce = principal.FindFirst("nonce")?.Value;
        var expectedNonce = context.Transaction.ProviderNonce ?? adapterState?.Nonce;
        if (string.IsNullOrWhiteSpace(expectedNonce) || !FixedTimeEquals(nonce, expectedNonce))
            throw new OpenIdConnectAuthenticationException("The identity provider nonce did not match the initiated request.");

        var issuer = principal.FindFirst("iss")?.Value ?? metadata.Issuer;
        var subject = principal.FindFirst("sub")?.Value;
        if (string.IsNullOrWhiteSpace(subject))
            throw new OpenIdConnectAuthenticationException("The identity provider response did not contain a subject.");

        var projectedClaims = ProjectClaims(principal, context.Connection.Connection.ClaimProjection);
        return new(new(issuer, subject, projectedClaims), projectedClaims, [], new(idToken));
    }

    public async ValueTask<ConnectionTestResult> TestAsync(ConnectionTestContext context, CancellationToken cancellationToken = default)

View on GitHub (pinned to fe9217bdfa)