elsa-workflows/elsa-core · error · OpenIdConnectAuthenticationException

The identity provider metadata was incomplete or unsafe.

Error message

The identity provider metadata was incomplete or unsafe.

What it means

Thrown by GetRequiredHttpsUri when a REQUIRED identity-provider metadata property (issuer, authorization_endpoint, or token_endpoint) is missing, is not a JSON string, or does not parse as an absolute HTTPS URL without userinfo or fragment. The adapter validates discovery metadata defensively so credentials are never sent to a non-HTTPS or attacker-controlled endpoint. It surfaces as OpenIdConnectAuthenticationException during metadata resolution.

Solutions

  1. Inspect the discovery document at the discoveryUrl and ensure issuer, authorization_endpoint and token_endpoint exist, are strings, and are absolute https:// URLs with no userinfo or fragment.
  2. Fix the identity provider's realm/app configuration so its advertised endpoints use https (correct external URL / forwarded headers).
  3. Ensure the reverse proxy forwards X-Forwarded-Proto so the IdP generates https endpoints.
  4. Point the adapter at the correct /.well-known/openid-configuration document (right issuer path, e.g. realm-scoped for Keycloak).

Example fix

// before
discoveryUrl: "http://idp.internal:8080/realms/app"  // no .well-known path, http endpoints
// after
discoveryUrl: "https://idp.example.com/realms/app/.well-known/openid-configuration"
Defensive patterns

Strategy: validation

Validate before calling

var json = await httpClient.GetStringAsync(discoveryUrl);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
string[] required = { "issuer", "authorization_endpoint", "token_endpoint" };
bool ok = required.All(p => root.TryGetProperty(p, out var v) && v.ValueKind == JsonValueKind.String
    && Uri.TryCreate(v.GetString(), UriKind.Absolute, out var u)
    && u.Scheme == "https" && string.IsNullOrEmpty(u.UserInfo) && string.IsNullOrEmpty(u.Fragment));
if (!ok) throw new Exception("Discovery metadata missing or unsafe required endpoints");

Try / catch

try { metadata = await adapter.ResolveMetadataAsync(ct); }
catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains("incomplete or unsafe"))
{
    logger.LogError(ex, "IdP discovery metadata invalid; check https endpoints for issuer/authorization/token");
}

Prevention

When it happens

Trigger: Calling ResolveMetadataAsync against a discovery document whose JSON lacks 'issuer', 'authorization_endpoint', or 'token_endpoint'; the property is a non-string JSON value (number/object/null); or the value is an http:// URL, a relative URL, or contains userinfo/fragment.

Common situations: IdP behind a proxy that advertises http:// endpoints; misconfigured issuer in the realm config; self-hosted Keycloak/Auth0 with endpoints left blank; pointing discoveryUrl at a custom JSON file with missing fields; downgrade attacks or mixed http metadata after TLS termination.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

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

    {
        try
        {
            return JsonDocument.Parse(payload);
        }
        catch (JsonException)
        {
            throw new OpenIdConnectAuthenticationException(safeMessage);
        }
    }
    private static bool TryGetParameter(IReadOnlyDictionary<string, IReadOnlyCollection<string>> values, string key, out string value) { value = values.TryGetValue(key, out var found) ? found.FirstOrDefault() ?? string.Empty : string.Empty; return !string.IsNullOrEmpty(value); }
    private static string CreateRandomValue() => Base64UrlEncoder.Encode(RandomNumberGenerator.GetBytes(32));
    private static string CreateCodeChallenge(string verifier) => Base64UrlEncoder.Encode(SHA256.HashData(Encoding.ASCII.GetBytes(verifier)));
    private static bool FixedTimeEquals(string? left, string? right) => left is not null && right is not null && CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(left), Encoding.UTF8.GetBytes(right));
    private static Uri WithQuery(Uri uri, IReadOnlyDictionary<string, string> values) => new(uri.AbsoluteUri + (uri.Query.Length == 0 ? "?" : "&") + string.Join("&", values.Select(x => $"{Uri.EscapeDataString(x.Key)}={Uri.EscapeDataString(x.Value)}")));
    private static Uri GetRequiredHttpsUri(JsonElement value, string property)
    {
        if (!value.TryGetProperty(property, out var item) || item.ValueKind != JsonValueKind.String || !TryGetHttpsUri(item.GetString(), out var uri))
            throw new OpenIdConnectAuthenticationException("The identity provider metadata was incomplete or unsafe.");
        return uri;
    }

    private static Uri? GetOptionalHttpsUri(JsonElement value, string property)
    {
        if (!value.TryGetProperty(property, out var item))
            return null;
        if (item.ValueKind != JsonValueKind.String || !TryGetHttpsUri(item.GetString(), out var uri))
            throw new OpenIdConnectAuthenticationException("The identity provider metadata contained an unsafe endpoint.");
        return uri;
    }

    private static bool TryGetHttpsUri(string? value, out Uri uri) => Uri.TryCreate(value, UriKind.Absolute, out uri!) && string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) && string.IsNullOrEmpty(uri.UserInfo) && string.IsNullOrEmpty(uri.Fragment);

    private sealed record ProviderMetadata(string Issuer, Uri AuthorizationEndpoint, Uri TokenEndpoint, Uri? UserInfoEndpoint, Uri? EndSessionEndpoint, Uri? JwksUri, JsonElement SigningKeys);
}

View on GitHub (pinned to fe9217bdfa)