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
- 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.
- Fix the identity provider's realm/app configuration so its advertised endpoints use https (correct external URL / forwarded headers).
- Ensure the reverse proxy forwards X-Forwarded-Proto so the IdP generates https endpoints.
- 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
- Verify the discovery JSON manually with curl before configuring the adapter
- Ensure the IdP's external URL / forwarded headers produce https endpoints
- Never point discoveryUrl at http:// or a hand-written metadata file
- Keep the issuer path (realm/tenant) correct in the discovery URL
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
- The identity provider metadata contained an unsafe endpoint.
- 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/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)