{"record":{"id":"a58e9d53d8607617","repo":"elsa-workflows/elsa-core","slug":"the-identity-provider-callback-issuer-did-not-match-the","errorCode":null,"errorMessage":"The identity provider callback issuer did not match the initiated request.","messagePattern":"The identity provider callback issuer did not match the initiated request\\.","errorType":"exception","errorClass":"OpenIdConnectAuthenticationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs","lineNumber":91,"sourceCode":"            [\"code_challenge_method\"] = \"S256\"\n        };\n\n        return new(WithQuery(metadata.AuthorizationEndpoint, query), state);\n    }\n\n    public async ValueTask<ExternalAuthenticationResult> AuthenticateCallbackAsync(ExternalCallbackContext context, CancellationToken cancellationToken = default)\n    {\n        if (TryGetParameter(context.Parameters, \"error\", out _))\n            throw new OpenIdConnectAuthenticationException(\"The identity provider rejected the authentication request.\");\n\n        if (!TryGetParameter(context.Parameters, \"state\", out var state) || !FixedTimeEquals(state, context.CorrelationState))\n            throw new OpenIdConnectAuthenticationException(\"The identity provider callback could not be correlated.\");\n\n        var settings = await GetSettingsAsync(context.Connection.Connection.AdapterSettings, cancellationToken);\n        var metadata = await ResolveMetadataAsync(settings, cancellationToken);\n        var adapterState = ReadAdapterState(context.Transaction.ProtectedPayload);\n        if (adapterState is not null && !string.Equals(adapterState.Issuer, metadata.Issuer, StringComparison.Ordinal))\n            throw new OpenIdConnectAuthenticationException(\"The identity provider callback issuer did not match the initiated request.\");\n\n        var idToken = await ExchangeCodeAsync(settings, metadata, context, adapterState?.CodeVerifier, cancellationToken);\n        var principal = await ValidateIdTokenAsync(idToken, settings, metadata, cancellationToken);\n        var nonce = principal.FindFirst(\"nonce\")?.Value;\n        var expectedNonce = context.Transaction.ProviderNonce ?? adapterState?.Nonce;\n        if (string.IsNullOrWhiteSpace(expectedNonce) || !FixedTimeEquals(nonce, expectedNonce))\n            throw new OpenIdConnectAuthenticationException(\"The identity provider nonce did not match the initiated request.\");\n\n        var issuer = principal.FindFirst(\"iss\")?.Value ?? metadata.Issuer;\n        var subject = principal.FindFirst(\"sub\")?.Value;\n        if (string.IsNullOrWhiteSpace(subject))\n            throw new OpenIdConnectAuthenticationException(\"The identity provider response did not contain a subject.\");\n\n        var projectedClaims = ProjectClaims(principal, context.Connection.Connection.ClaimProjection);\n        return new(new(issuer, subject, projectedClaims), projectedClaims, [], new(idToken));\n    }\n\n    public async ValueTask<ConnectionTestResult> TestAsync(ConnectionTestContext context, CancellationToken cancellationToken = default)","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs#L73-L109","documentation":"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.","triggerScenarios":"AuthenticateCallbackAsync where ReadAdapterState returns a non-null state whose Issuer differs from metadata.Issuer resolved via ResolveMetadataAsync.","commonSituations":"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.","solutions":["Confirm the connection's DiscoveryUrl/Issuer settings were not edited between initiation and callback; restart the login flow after any change.","Check the provider's discovery document issuer matches the configured issuer exactly (case and trailing slash matter — comparison is ordinal).","In multi-tenant/realm setups, make sure the realm-specific discovery URL is used consistently for the whole flow."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"var meta = await ResolveMetadataAsync(settings); if (!string.Equals(savedIssuer, meta.Issuer, StringComparison.Ordinal)) logger.LogError(\"Issuer drift: {Saved} vs {Current}\", savedIssuer, meta.Issuer);","typeGuard":null,"tryCatchPattern":"try { await adapter.AuthenticateCallbackAsync(context); } catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains(\"issuer\")) { logger.LogError(ex, \"OIDC issuer mismatch\"); throw; }","preventionTips":["Pin the issuer/discovery configuration per environment and avoid changing it mid-flight.","Compare issuer strings with exact ordinal semantics (watch trailing slashes).","In multi-tenant setups, keep realm-specific discovery URLs consistent per connection."],"tags":["openid-connect","authentication","issuer-mismatch"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}