elsa-workflows/elsa-core · error · OpenIdConnectAuthenticationException

The identity provider nonce did not match the initiated…

Error message

The identity provider nonce did not match the initiated request.

What it means

After exchanging the code and validating the id_token, the adapter reads the 'nonce' claim from the validated principal and compares it (constant-time) with the expected nonce from the transaction's ProviderNonce or the adapter state. If the nonce is absent in the token or does not match, replay protection fails and this exception is thrown.

Solutions

  1. Verify the provider includes the nonce claim in id_tokens (test with jwt.io); if not, use a provider or trust mode that supports nonce.
  2. Initiate a fresh login flow rather than retrying a stale callback URL, ensuring nonce is captured at initiation.
  3. Check that the protected transaction payload is not being truncated or altered by middleware between initiation and callback.
Defensive patterns

Strategy: validation

Validate before calling

var token = DecodeJwt(idToken); if (string.IsNullOrEmpty(token.Payload.Nonce)) throw new InvalidOperationException("Provider id_token is missing nonce; flow cannot be verified.");

Try / catch

try { await adapter.AuthenticateCallbackAsync(context); } catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains("nonce")) { return Results.Redirect("/login?reason=nonce-mismatch"); }

Prevention

When it happens

Trigger: AuthenticateCallbackAsync where the id_token lacks a 'nonce' claim, expectedNonce is null/whitespace, or FixedTimeEquals(nonce, expectedNonce) returns false.

Common situations: Provider does not echo the nonce into the id_token; a replayed id_token from an earlier authorization; nonce lost because the transaction payload was corrupted or the flow's ProviderNonce and adapterState.Nonce were both missing.

Related errors


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

Appendix: source

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

    {
        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)
    {
        var settings = await GetSettingsAsync(context.Connection.Connection.AdapterSettings, cancellationToken);
        _ = await ResolveMetadataAsync(settings, cancellationToken);
        return new(ConnectionObservationStatus.Succeeded, "reachable", "Provider metadata was resolved.", []);
    }

    public async ValueTask<ExternalLogoutRequest?> CreateLogoutRequestAsync(ExternalLogoutContext context, CancellationToken cancellationToken = default)

View on GitHub (pinned to fe9217bdfa)