elsa-workflows/elsa-core · error · OpenIdConnectAuthenticationException
The identity provider callback did not contain an…
Error message
The identity provider callback did not contain an authorization code.
What it means
ExchangeCodeAsync expects the authorization-code callback parameters to contain a 'code' value to redeem at the token endpoint. If the callback has no 'code' parameter (and passed the earlier error/state checks), the adapter throws this exception because there is nothing to exchange.
Solutions
- Check the callback URL the provider used — confirm a 'code' query parameter is present on the redirect.
- Ensure the authorization request uses response_type=code (authorization code flow) so the provider returns a code.
- Guard against duplicate callback processing: each code can only be exchanged once, so reloads of the callback will lack a fresh code — restart the login flow.
Example fix
// before: initiating with implicit/hybrid flow
new Dictionary<string, string> { ["response_type"] = "id_token", ["client_id"] = clientId }
// after: authorization code flow
new Dictionary<string, string> { ["response_type"] = "code", ["client_id"] = clientId, ["scope"] = "openid" } Defensive patterns
Strategy: validation
Validate before calling
if (!Request.Query.ContainsKey("code")) return Results.Redirect("/login?reason=missing-code"); Try / catch
try { await adapter.AuthenticateCallbackAsync(context); } catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains("authorization code")) { logger.LogWarning(ex, "Callback without code; restarting login"); return Challenge(); } Prevention
- Use response_type=code in the authorization request.
- Treat the callback endpoint as one-shot; redirect reloads back to the login initiation.
- Confirm the provider's redirect sends the code as a query parameter your middleware preserves.
When it happens
Trigger: Calling AuthenticateCallbackAsync (which invokes ExchangeCodeAsync) where TryGetParameter(context.Parameters, "code") fails.
Common situations: Provider redirected back with response_mode that puts the code somewhere not captured; response_type misconfigured (e.g. id_token-only flow); a spurious callback hit (bot refresh, user reloading the callback URL after the code was already consumed and removed).
Related errors
- The identity provider rejected the authentication request.
- The provider client secret is unavailable.
- 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/d967e53b9e4ba2c9.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs:156
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>
{
["grant_type"] = "authorization_code",
["code"] = code,
["redirect_uri"] = GetCallbackUri(context.Connection, context.Transaction.Purpose).AbsoluteUri
};
if (verifier is not null)
values["code_verifier"] = verifier;
if (!context.Secrets.TryGetValue("clientSecret", out var secret))
throw new OpenIdConnectAuthenticationException("The provider client secret is unavailable.");
IReadOnlyDictionary<string, string>? headers = null;
if (settings.ClientAuthenticationMethod == OpenIdConnectClientAuthenticationMethod.ClientSecretPost)
{
values["client_id"] = settings.ClientId;
values["client_secret"] = secret.Value.Reveal();
}
elseView on GitHub (pinned to fe9217bdfa)