elsa-workflows/elsa-core · error · OpenIdConnectAuthenticationException

The identity provider signing keys could not be resolved.

Error message

The identity provider signing keys could not be resolved.

What it means

The JWKS URI exists and was fetched, but the HTTP response was not successful, so signing keys could not be downloaded. ID-token signature validation cannot proceed without keys. The underlying status code is not embedded in the message, so check provider availability and network path.

Solutions

  1. Confirm the app host can reach the jwks_uri (curl it from the server; check DNS, proxy, and firewall/egress rules).
  2. Check the returned status code via provider/proxy logs to distinguish 404 vs 403 vs 5xx.
  3. Fix TLS trust (install the provider's CA chain into the host trust store) if the fetch fails on certificate validation.
  4. Add retry with backoff for transient 5xx during provider key rotation windows.
  5. Verify the jwks_uri path in the discovery document matches the provider's actual JWKS route.

Example fix

// before: IDP not reachable from container
issuer: "https://internal-idp.local"  // resolvable only on the corporate LAN
// after: ensure egress DNS/route or use a reachable address
issuer: "https://idp.internal.example.com" // with DNS + firewall entry for the app host
Defensive patterns

Strategy: retry

Validate before calling

using var ping = await httpClient.GetAsync(jwksUri, HttpCompletionOption.ResponseHeadersRead, ct);
if (!ping.IsSuccessStatusCode)
    throw new InvalidOperationException($"JWKS endpoint {jwksUri} returned {ping.StatusCode}; fix reachability before sign-in.");

Try / catch

try { await adapter.ValidateIdTokenAsync(idToken, settings, metadata, ct); }
catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains("could not be resolved"))
{
    await Task.Delay(TimeSpan.FromSeconds(2), ct); // retry once for transient provider unavailability
    // retry the flow or return 502
    return Results.Problem("Identity provider signing keys are temporarily unavailable.", statusCode: 502);
}

Prevention

When it happens

Trigger: GetSigningKeysAsync's providerHttpClient.GetAsync to the jwks_uri returns a non-2xx status: 404 (wrong jwks path), 403 (WAF/IP blocking), 5xx (provider outage), or connection-level failure surfaced as non-success.

Common situations: Server cannot reach the identity provider (egress firewall, private IDP not routable from the app host); TLS trust issues terminating as failed fetches; provider temporarily down during key rotation; self-hosted Keycloak behind a proxy returning 404 for /.well-known/jwks.json.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

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

    private Uri GetCallbackUri(EffectiveIdentityProviderConnection connection, BrokerTransactionPurpose purpose)
    {
        var baseUri = options.Value.Redirects.ExternalCallbackBaseUri ?? throw new OpenIdConnectAuthenticationException("The deployment callback base URI is not configured.");
        return ExternalAuthenticationCallbackUris.GetAuthorizationCallbackUri(baseUri, connection.Connection, purpose);
    }

    private Uri GetLogoutCallbackUri(EffectiveIdentityProviderConnection connection)
    {
        var baseUri = options.Value.Redirects.ExternalCallbackBaseUri ?? throw new OpenIdConnectAuthenticationException("The deployment callback base URI is not configured.");
        return ExternalAuthenticationCallbackUris.GetLogoutCallbackUri(baseUri, connection.Connection.Key);
    }

    private async Task<IEnumerable<SecurityKey>> GetSigningKeysAsync(Uri? jwksUri, CancellationToken cancellationToken)
    {
        if (jwksUri is null)
            throw new OpenIdConnectAuthenticationException("The identity provider did not provide signing keys.");
        var response = await providerHttpClient.GetAsync(jwksUri, ProviderResponseKind.SigningKeys, cancellationToken);
        if (!response.IsSuccessStatusCode)
            throw new OpenIdConnectAuthenticationException("The identity provider signing keys could not be resolved.");
        try
        {
            return new JsonWebKeySet(response.ReadBodyAsUtf8()).Keys;
        }
        catch (JsonException)
        {
            throw new OpenIdConnectAuthenticationException("The identity provider signing keys were invalid.");
        }
    }

    private static IReadOnlyDictionary<string, IReadOnlyCollection<string>> ProjectClaims(System.Security.Claims.ClaimsPrincipal principal, ClaimProjection projection)
    {
        if (projection.MaximumClaimCount <= 0 || projection.MaximumValueLength <= 0 || projection.MaximumTotalBytes <= 0)
            return new Dictionary<string, IReadOnlyCollection<string>>(StringComparer.Ordinal);

        var allowed = projection.AllowedClaimTypes ?? new HashSet<string>();
        var result = new Dictionary<string, List<string>>(StringComparer.Ordinal);
        var count = 0;

View on GitHub (pinned to fe9217bdfa)