{"record":{"id":"027c33da0b46815b","repo":"elsa-workflows/elsa-core","slug":"the-identity-provider-metadata-was-incomplete-or-unsafe","errorCode":null,"errorMessage":"The identity provider metadata was incomplete or unsafe.","messagePattern":"The identity provider metadata was incomplete or unsafe\\.","errorType":"exception","errorClass":"OpenIdConnectAuthenticationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs","lineNumber":293,"sourceCode":"    {\n        try\n        {\n            return JsonDocument.Parse(payload);\n        }\n        catch (JsonException)\n        {\n            throw new OpenIdConnectAuthenticationException(safeMessage);\n        }\n    }\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":275,"sourceCodeEnd":310,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs#L275-L310","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\ndiscoveryUrl: \"http://idp.internal:8080/realms/app\"  // no .well-known path, http endpoints\n// after\ndiscoveryUrl: \"https://idp.example.com/realms/app/.well-known/openid-configuration\"","handlingStrategy":"validation","validationCode":"var json = await httpClient.GetStringAsync(discoveryUrl);\nusing var doc = JsonDocument.Parse(json);\nvar root = doc.RootElement;\nstring[] required = { \"issuer\", \"authorization_endpoint\", \"token_endpoint\" };\nbool ok = required.All(p => root.TryGetProperty(p, out var v) && v.ValueKind == JsonValueKind.String\n    && Uri.TryCreate(v.GetString(), UriKind.Absolute, out var u)\n    && u.Scheme == \"https\" && string.IsNullOrEmpty(u.UserInfo) && string.IsNullOrEmpty(u.Fragment));\nif (!ok) throw new Exception(\"Discovery metadata missing or unsafe required endpoints\");","typeGuard":null,"tryCatchPattern":"try { metadata = await adapter.ResolveMetadataAsync(ct); }\ncatch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains(\"incomplete or unsafe\"))\n{\n    logger.LogError(ex, \"IdP discovery metadata invalid; check https endpoints for issuer/authorization/token\");\n}","preventionTips":["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"],"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"}