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
- 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.
- Fix the IdP's external-URL/forwarded-header settings so all advertised endpoints are https.
- If only SSO (authorization/token) is needed, use an IdP/realm config that omits the broken optional endpoint rather than emitting an invalid one.
- 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
- Audit all advertised endpoints in the discovery document for https
- Remove unused optional endpoints rather than leaving broken http ones
- Fix TLS termination/forwarded headers so the IdP emits https URLs
- Refresh cached metadata from the canonical discovery URL
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
- The identity provider metadata was incomplete or unsafe.
- The identity provider rejected the authentication request.
- The identity provider callback could not be correlated.
- The identity provider callback issuer did not match the…
- The identity provider nonce did not match the initiated…
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)