git-ecosystem/git-credential-manager · critical · Trace2OAuth2Exception

Invalid ' ' in response; does not match the request.

Error message

Invalid '{OAuth2Constants.AuthorizationGrantResponse.StateParameter}' in response; does not match the request.

What it means

Companion to the missing-state check in GetAuthorizationCodeAsync: the response did contain a state parameter, but its value does not match the one sent in the authorization request (compared ordinally). Per OAuth2 this indicates a badly implemented authorization server, a CSRF attempt, or a replay — the client must terminate the flow.

Solutions

  1. Ensure the exact same state string passed to the authorization request is passed to GetAuthorizationCodeAsync.
  2. Always use the fresh redirect/callback URL from the current login attempt; never reuse URLs from earlier flows.
  3. If running concurrent logins, keep per-attempt state and match each callback to its request.
  4. Investigate the identity provider if it legitimately fails to echo state — it is non-compliant with OAuth2.

Example fix

// before
var state = Guid.NewGuid().ToString("N");
// ... browser flow ...
await client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, verifier, cachedCallbackUrl); // old state
// after
await client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, verifier, currentCallbackUrl); // same attempt's URL
Defensive patterns

Strategy: try-catch

Validate before calling

var responseParams = ParseResponseParams(callbackUrl);
if (responseParams.TryGetValue("state", out var s) && !StringComparer.Ordinal.Equals(s, state))
    throw new InvalidOperationException("State mismatch: possible CSRF/replay; aborting.");

Try / catch

try
{
    result = await client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, verifier, callbackUrl);
}
catch (Trace2OAuth2Exception ex) when (ex.Message.Contains("does not match the request"))
{
    // state mismatch: CSRF or stale callback; discard and restart login
    return AuthFailure.StateMismatch;
}

Prevention

When it happens

Trigger: Calling GetAuthorizationCodeAsync with a response whose 'state' value differs from the state argument — e.g. reusing a redirect URL from a previous login attempt, hardcoding state in one place, or an IdP generating its own state.

Common situations: Replaying an old callback URL after the state was regenerated; multiple concurrent sign-in attempts where callbacks got crossed; a custom/stub IdP echoing a different or empty state; copy-pasting a callback URL from a prior session.

Related errors


AI-assisted analysis of git-ecosystem/git-credential-manager@e8ce762cd0 (2026-09-11). Data as JSON: /api/errors/567b16a1f4a63c18. Report an issue: GitHub.

Appendix: source

Thrown at src/Core/Authentication/OAuth/OAuth2Client.cs:186

            Uri authorizationUri = authorizationUriBuilder.Uri;

            // Open the browser at the request URI to start the authorization code grant flow, and
            // intercept the response parameters delivered to the redirect URI.
            IDictionary<string, string> responseParams =
                await browser.GetAuthenticationResponseAsync(authorizationUri, redirectUri, _responseMode, ct);

            // Check for errors serious enough we should terminate the flow, such as if the state value returned does
            // not match the one we passed. This indicates a badly implemented Authorization Server, or worse, some
            // form of failed MITM or replay attack.
            if (!responseParams.TryGetValue(OAuth2Constants.AuthorizationGrantResponse.StateParameter, out string replyState))
            {
                throw new Trace2OAuth2Exception(_trace2,
                    $"Missing '{OAuth2Constants.AuthorizationGrantResponse.StateParameter}' in response.");
            }
            if (!StringComparer.Ordinal.Equals(state, replyState))
            {
                throw new Trace2OAuth2Exception(_trace2,
                    $"Invalid '{OAuth2Constants.AuthorizationGrantResponse.StateParameter}' in response; does not match the request.");
            }

            // We expect to have the auth code in the response otherwise terminate the flow (we failed authentication for some reason)
            if (!responseParams.TryGetValue(OAuth2Constants.AuthorizationGrantResponse.AuthorizationCodeParameter, out string authCode))
            {
                throw new Trace2OAuth2Exception(_trace2,
                    $"Missing '{OAuth2Constants.AuthorizationGrantResponse.AuthorizationCodeParameter}' in response.");
            }

            return new OAuth2AuthorizationCodeResult(authCode, redirectUri, codeVerifier);
        }

        public async Task<OAuth2DeviceCodeResult> GetDeviceCodeAsync(IEnumerable<string> scopes, CancellationToken ct)
        {
            var label = "get device code";
            using IDisposable region = _trace2.CreateRegion(OAuth2Constants.Trace2Category, label);

View on GitHub (pinned to e8ce762cd0)