elsa-workflows/elsa-core · error · OpenIdConnectAuthenticationException

The OpenID Connect discovery URL is required.

Error message

The OpenID Connect discovery URL is required.

What it means

ResolveMetadataAsync only fetches a discovery document when TrustMode is not Manual. In discovery mode, settings.DiscoveryUrl is the source of the provider metadata; if it is null, the adapter cannot discover the provider and throws this exception instead of attempting an HTTP request.

Solutions

  1. Set discoveryUrl in the connection settings (typically https://<authority>/.well-known/openid-configuration).
  2. Alternatively switch trustMode to "Manual" and provide issuer, authorizationEndpoint and tokenEndpoint explicitly.
  3. Re-test the connection with TestAsync after fixing the settings.

Example fix

// before
{ "trustMode": "Discovery", "clientId": "my-client" }
// after
{ "trustMode": "Discovery", "discoveryUrl": "https://idp.example.com/.well-known/openid-configuration", "clientId": "my-client" }
Defensive patterns

Strategy: validation

Validate before calling

if (settings.TrustMode != "Manual" && string.IsNullOrWhiteSpace(settings.DiscoveryUrl)) throw new InvalidOperationException("Discovery trust mode requires a discoveryUrl.");

Try / catch

try { await adapter.TestAsync(testContext); } catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains("discovery URL is required")) { return Results.ValidationProblem(new Dictionary<string, string[]> { ["discoveryUrl"] = ["Discovery URL is required for discovery trust mode."] }); }

Prevention

When it happens

Trigger: Calling ResolveMetadataAsync (via initiation, callback, or TestAsync) with settings.TrustMode != Manual and settings.DiscoveryUrl unset.

Common situations: Creating a connection and choosing discovery-based trust but forgetting to fill in the discovery URL; copying a Manual-mode template into a discovery-mode connection.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/a12bcbfc9c22b68d. Report an issue: GitHub.

Appendix: source

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

        if (context.UpstreamLogoutHint is not null)
            query["id_token_hint"] = context.UpstreamLogoutHint.Reveal();
        return new(WithQuery(metadata.EndSessionEndpoint, query), []);
    }

    private async Task<OpenIdConnectConnectionSettings> GetSettingsAsync(JsonElement settings, CancellationToken cancellationToken)
    {
        if (!settingsParser.TryParse(settings, out var parsed, out _))
            throw new OpenIdConnectAuthenticationException("The OpenID Connect connection configuration is invalid.");
        await Task.CompletedTask;
        return parsed!;
    }

    private async Task<ProviderMetadata> ResolveMetadataAsync(OpenIdConnectConnectionSettings settings, CancellationToken cancellationToken)
    {
        if (settings.TrustMode == OpenIdConnectTrustMode.Manual)
            return new(settings.Issuer!, settings.AuthorizationEndpoint!, settings.TokenEndpoint!, settings.UserInfoEndpoint, settings.EndSessionEndpoint, settings.JwksUri, settings.SigningKeys);

        var address = settings.DiscoveryUrl ?? throw new OpenIdConnectAuthenticationException("The OpenID Connect discovery URL is required.");
        var response = await providerHttpClient.GetAsync(address, ProviderResponseKind.Discovery, cancellationToken);
        if (!response.IsSuccessStatusCode)
            throw new OpenIdConnectAuthenticationException("The identity provider metadata could not be resolved.");
        using var document = ParseProviderJson(response.Body, "The identity provider metadata could not be resolved.");
        var root = document.RootElement;
        var issuer = GetRequiredHttpsUri(root, "issuer").AbsoluteUri.TrimEnd('/');
        var authorizationEndpoint = GetRequiredHttpsUri(root, "authorization_endpoint");
        var tokenEndpoint = GetRequiredHttpsUri(root, "token_endpoint");
        return new(issuer, authorizationEndpoint, tokenEndpoint, GetOptionalHttpsUri(root, "userinfo_endpoint"), GetOptionalHttpsUri(root, "end_session_endpoint"), GetOptionalHttpsUri(root, "jwks_uri"), default);
    }

    private async Task<string> ExchangeCodeAsync(OpenIdConnectConnectionSettings settings, ProviderMetadata metadata, ExternalCallbackContext context, string? verifier, CancellationToken cancellationToken)
    {
        if (!TryGetParameter(context.Parameters, "code", out var code))
            throw new OpenIdConnectAuthenticationException("The identity provider callback did not contain an authorization code.");

        var values = new Dictionary<string, string>
        {

View on GitHub (pinned to fe9217bdfa)