{"record":{"id":"d967e53b9e4ba2c9","repo":"elsa-workflows/elsa-core","slug":"the-identity-provider-callback-did-not-contain-an","errorCode":null,"errorMessage":"The identity provider callback did not contain an authorization code.","messagePattern":"The identity provider callback did not contain an authorization code\\.","errorType":"exception","errorClass":"OpenIdConnectAuthenticationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs","lineNumber":156,"sourceCode":"        if (settings.TrustMode == OpenIdConnectTrustMode.Manual)\n            return new(settings.Issuer!, settings.AuthorizationEndpoint!, settings.TokenEndpoint!, settings.UserInfoEndpoint, settings.EndSessionEndpoint, settings.JwksUri, settings.SigningKeys);\n\n        var address = settings.DiscoveryUrl ?? throw new OpenIdConnectAuthenticationException(\"The OpenID Connect discovery URL is required.\");\n        var response = await providerHttpClient.GetAsync(address, ProviderResponseKind.Discovery, cancellationToken);\n        if (!response.IsSuccessStatusCode)\n            throw new OpenIdConnectAuthenticationException(\"The identity provider metadata could not be resolved.\");\n        using var document = ParseProviderJson(response.Body, \"The identity provider metadata could not be resolved.\");\n        var root = document.RootElement;\n        var issuer = GetRequiredHttpsUri(root, \"issuer\").AbsoluteUri.TrimEnd('/');\n        var authorizationEndpoint = GetRequiredHttpsUri(root, \"authorization_endpoint\");\n        var tokenEndpoint = GetRequiredHttpsUri(root, \"token_endpoint\");\n        return new(issuer, authorizationEndpoint, tokenEndpoint, GetOptionalHttpsUri(root, \"userinfo_endpoint\"), GetOptionalHttpsUri(root, \"end_session_endpoint\"), GetOptionalHttpsUri(root, \"jwks_uri\"), default);\n    }\n\n    private async Task<string> ExchangeCodeAsync(OpenIdConnectConnectionSettings settings, ProviderMetadata metadata, ExternalCallbackContext context, string? verifier, CancellationToken cancellationToken)\n    {\n        if (!TryGetParameter(context.Parameters, \"code\", out var code))\n            throw new OpenIdConnectAuthenticationException(\"The identity provider callback did not contain an authorization code.\");\n\n        var values = new Dictionary<string, string>\n        {\n            [\"grant_type\"] = \"authorization_code\",\n            [\"code\"] = code,\n            [\"redirect_uri\"] = GetCallbackUri(context.Connection, context.Transaction.Purpose).AbsoluteUri\n        };\n        if (verifier is not null)\n            values[\"code_verifier\"] = verifier;\n        if (!context.Secrets.TryGetValue(\"clientSecret\", out var secret))\n            throw new OpenIdConnectAuthenticationException(\"The provider client secret is unavailable.\");\n        IReadOnlyDictionary<string, string>? headers = null;\n        if (settings.ClientAuthenticationMethod == OpenIdConnectClientAuthenticationMethod.ClientSecretPost)\n        {\n            values[\"client_id\"] = settings.ClientId;\n            values[\"client_secret\"] = secret.Value.Reveal();\n        }\n        else","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication.OpenIdConnect/Services/OpenIdConnectExternalAuthenticationAdapter.cs#L138-L174","documentation":"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.","triggerScenarios":"Calling AuthenticateCallbackAsync (which invokes ExchangeCodeAsync) where TryGetParameter(context.Parameters, \"code\") fails.","commonSituations":"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).","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."],"exampleFix":"// before: initiating with implicit/hybrid flow\nnew Dictionary<string, string> { [\"response_type\"] = \"id_token\", [\"client_id\"] = clientId }\n// after: authorization code flow\nnew Dictionary<string, string> { [\"response_type\"] = \"code\", [\"client_id\"] = clientId, [\"scope\"] = \"openid\" }","handlingStrategy":"validation","validationCode":"if (!Request.Query.ContainsKey(\"code\")) return Results.Redirect(\"/login?reason=missing-code\");","typeGuard":null,"tryCatchPattern":"try { await adapter.AuthenticateCallbackAsync(context); } catch (OpenIdConnectAuthenticationException ex) when (ex.Message.Contains(\"authorization code\")) { logger.LogWarning(ex, \"Callback without code; restarting login\"); return Challenge(); }","preventionTips":["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."],"tags":["openid-connect","authentication","oauth","authorization-code"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}