elsa-workflows/elsa-core · error · OpenIdConnectAuthenticationException

The identity provider token response did not contain an ID…

Error message

The identity provider token response did not contain an ID token.

What it means

ParseProviderJson succeeded (valid JSON) but the token response body has no "id_token" property that is a non-empty string, so the adapter cannot proceed to ID-token validation. OpenID Connect providers should always return id_token for the authorization_code flow; its absence means the grant type or scopes are misconfigured on the provider side.

Solutions

  1. Add the "openid" scope (and any required claims scopes) to the authorization request so the provider issues an ID token.
  2. Verify the provider is configured as an OpenID Connect provider and supports the authorization_code flow with ID tokens.
  3. Inspect the token response body to see what is returned instead of id_token (often only access_token/token_type).
  4. If the provider cannot return id_token, switch to a userinfo-based flow instead of relying on this adapter.

Example fix

// before
scopes: ["profile", "email"]
// after
scopes: ["openid", "profile", "email"]
Defensive patterns

Strategy: validation

Validate before calling

// Ensure 'openid' scope is present in the authorization request before redirecting
if (!requestedScopes.Contains("openid", StringComparer.Ordinal))
    throw new InvalidOperationException("The authorization request must include the 'openid' scope to receive an ID token.");

Try / catch

try { await broker.ExchangeCodeAsync(transaction, code, ct); }
catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains("ID token")) { logger.LogError("Provider returned no id_token; check scopes/flow config"); return Results.Problem("Provider did not issue an ID token.", statusCode: 502); }

Prevention

When it happens

Trigger: ExchangeCodeAsync receives a 2xx token response whose JSON lacks an id_token member, has it as a non-string JSON value, or as an empty/whitespace string.

Common situations: Provider returning a plain OAuth2 access-token response because the authorization request omitted the "openid" scope; hybrid/response-type misconfiguration; provider configured for token-only response types; custom token endpoint stubs in tests returning only access_token.

Related errors


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

Appendix: source

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

        if (verifier is not null)
            values["code_verifier"] = verifier;
        if (!context.Secrets.TryGetValue("clientSecret", out var secret))
            throw new OpenIdConnectAuthenticationException("The provider client secret is unavailable.");
        IReadOnlyDictionary<string, string>? headers = null;
        if (settings.ClientAuthenticationMethod == OpenIdConnectClientAuthenticationMethod.ClientSecretPost)
        {
            values["client_id"] = settings.ClientId;
            values["client_secret"] = secret.Value.Reveal();
        }
        else
            headers = new Dictionary<string, string> { ["Authorization"] = $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{FormUrlEncode(settings.ClientId)}:{FormUrlEncode(secret.Value.Reveal())}"))}" };

        var response = await providerHttpClient.PostFormAsync(metadata.TokenEndpoint, values, headers, ProviderResponseKind.Token, cancellationToken);
        if (!response.IsSuccessStatusCode)
            throw new OpenIdConnectAuthenticationException("The identity provider token exchange failed.");
        using var payload = ParseProviderJson(response.Body, "The identity provider token response was invalid.");
        if (!payload.RootElement.TryGetProperty("id_token", out var idToken) || idToken.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(idToken.GetString()))
            throw new OpenIdConnectAuthenticationException("The identity provider token response did not contain an ID token.");
        return idToken.GetString()!;
    }

    private static string FormUrlEncode(string value) => Uri.EscapeDataString(value).Replace("%20", "+", StringComparison.Ordinal);

    private async Task<System.Security.Claims.ClaimsPrincipal> ValidateIdTokenAsync(string idToken, OpenIdConnectConnectionSettings settings, ProviderMetadata metadata, CancellationToken cancellationToken)
    {
        var signingKeys = metadata.SigningKeys.ValueKind == JsonValueKind.Object
            ? new JsonWebKeySet(metadata.SigningKeys.GetRawText()).Keys
            : await GetSigningKeysAsync(metadata.JwksUri, cancellationToken);
        var validation = await new JsonWebTokenHandler { MapInboundClaims = false }.ValidateTokenAsync(idToken, new()
        {
            ValidateIssuer = true,
            ValidIssuer = metadata.Issuer,
            ValidateAudience = true,
            ValidAudience = settings.ClientId,
            ValidateIssuerSigningKey = true,
            IssuerSigningKeys = signingKeys,

View on GitHub (pinned to fe9217bdfa)