{"record":{"id":"780d6a1cb2373fac","repo":"elsa-workflows/elsa-core","slug":"the-identity-provider-metadata-contained-an-unsafe-endpoint","errorCode":null,"errorMessage":"The identity provider metadata contained an unsafe endpoint.","messagePattern":"The identity provider metadata contained an unsafe endpoint\\.","errorType":"exception","errorClass":"OpenIdConnectAuthenticationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs","lineNumber":302,"sourceCode":"    }\n    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); }\n    private static string CreateRandomValue() => Base64UrlEncoder.Encode(RandomNumberGenerator.GetBytes(32));\n    private static string CreateCodeChallenge(string verifier) => Base64UrlEncoder.Encode(SHA256.HashData(Encoding.ASCII.GetBytes(verifier)));\n    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));\n    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)}\")));\n    private static Uri GetRequiredHttpsUri(JsonElement value, string property)\n    {\n        if (!value.TryGetProperty(property, out var item) || item.ValueKind != JsonValueKind.String || !TryGetHttpsUri(item.GetString(), out var uri))\n            throw new OpenIdConnectAuthenticationException(\"The identity provider metadata was incomplete or unsafe.\");\n        return uri;\n    }\n\n    private static Uri? GetOptionalHttpsUri(JsonElement value, string property)\n    {\n        if (!value.TryGetProperty(property, out var item))\n            return null;\n        if (item.ValueKind != JsonValueKind.String || !TryGetHttpsUri(item.GetString(), out var uri))\n            throw new OpenIdConnectAuthenticationException(\"The identity provider metadata contained an unsafe endpoint.\");\n        return uri;\n    }\n\n    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);\n\n    private sealed record ProviderMetadata(string Issuer, Uri AuthorizationEndpoint, Uri TokenEndpoint, Uri? UserInfoEndpoint, Uri? EndSessionEndpoint, Uri? JwksUri, JsonElement SigningKeys);\n}\n","sourceCodeStart":284,"sourceCodeEnd":310,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs#L284-L310","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// metadata before\n\"end_session_endpoint\": \"http://idp.example.com/logout\"\n// after\n\"end_session_endpoint\": \"https://idp.example.com/logout\"","handlingStrategy":"validation","validationCode":"using var doc = JsonDocument.Parse(await httpClient.GetStringAsync(discoveryUrl));\nforeach (var prop in new[] { \"userinfo_endpoint\", \"end_session_endpoint\", \"jwks_uri\" })\n{\n    if (doc.RootElement.TryGetProperty(prop, out var v) && v.ValueKind == JsonValueKind.String)\n    {\n        var ok = Uri.TryCreate(v.GetString(), UriKind.Absolute, out var u) && u.Scheme == \"https\"\n                 && string.IsNullOrEmpty(u.UserInfo) && string.IsNullOrEmpty(u.Fragment);\n        if (!ok) Console.WriteLine($\"Optional endpoint {prop} is unsafe: {v.GetString()}\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try { metadata = await adapter.ResolveMetadataAsync(ct); }\ncatch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains(\"unsafe endpoint\"))\n{\n    logger.LogError(ex, \"Optional IdP endpoint failed the https safety check; fix or omit it in metadata\");\n}","preventionTips":["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"],"tags":["openid-connect","https","metadata","authentication"],"backgroundTag":"invalid-url-format","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}