elsa-workflows/elsa-core · error · OpenIdConnectAuthenticationException

The identity provider metadata contained an unsafe endpoint.

Error message

The identity provider metadata contained an unsafe endpoint.

What it means

Thrown by GetOptionalHttpsUri when an OPTIONAL metadata endpoint property (e.g. userinfo_endpoint, end_session_endpoint, jwks_uri) is present but is not a JSON string, or fails the strict https-only URL check (must be absolute https with no userinfo and no fragment). Unlike the required variant, absent properties simply yield null; only present-but-unsafe values throw.

Solutions

  1. Open the discovery JSON and correct the offending optional endpoint to an absolute https:// URL without userinfo/fragment, or remove the field if the feature is unused.
  2. Fix the IdP's external-URL/forwarded-header settings so all advertised endpoints are https.
  3. If only SSO (authorization/token) is needed, use an IdP/realm config that omits the broken optional endpoint rather than emitting an invalid one.
  4. Clear any cached/stale metadata copy and re-fetch from the canonical discovery URL.

Example fix

// metadata before
"end_session_endpoint": "http://idp.example.com/logout"
// after
"end_session_endpoint": "https://idp.example.com/logout"
Defensive patterns

Strategy: validation

Validate before calling

using var doc = JsonDocument.Parse(await httpClient.GetStringAsync(discoveryUrl));
foreach (var prop in new[] { "userinfo_endpoint", "end_session_endpoint", "jwks_uri" })
{
    if (doc.RootElement.TryGetProperty(prop, out var v) && v.ValueKind == JsonValueKind.String)
    {
        var ok = Uri.TryCreate(v.GetString(), UriKind.Absolute, out var u) && u.Scheme == "https"
                 && string.IsNullOrEmpty(u.UserInfo) && string.IsNullOrEmpty(u.Fragment);
        if (!ok) Console.WriteLine($"Optional endpoint {prop} is unsafe: {v.GetString()}");
    }
}

Try / catch

try { metadata = await adapter.ResolveMetadataAsync(ct); }
catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains("unsafe endpoint"))
{
    logger.LogError(ex, "Optional IdP endpoint failed the https safety check; fix or omit it in metadata");
}

Prevention

When it happens

Trigger: ResolveMetadataAsync encounters an optional endpoint field in the discovery document whose value is a non-string JSON type, an http:// URL, a relative URL, or an https URL containing user info or a fragment.

Common situations: IdP metadata exposing http endpoints behind a TLS-terminating proxy; a jwks_uri pointing at an internal http address; hand-crafted or cached metadata with malformed URLs; misconfigured logout redirect URIs.

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/780d6a1cb2373fac. Report an issue: GitHub.

Appendix: source

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

    }
    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)