{"record":{"id":"79aa4b34df2c2e83","repo":"git-ecosystem/git-credential-manager","slug":"missing-oauth2constants-authorizationgrantrespon","errorCode":null,"errorMessage":"Missing '{OAuth2Constants.AuthorizationGrantResponse.StateParameter}' in response.","messagePattern":"Missing '(.+?)' in response\\.","errorType":"exception","errorClass":"Trace2OAuth2Exception","httpStatus":null,"severity":"critical","filePath":"src/Core/Authentication/OAuth/OAuth2Client.cs","lineNumber":181,"sourceCode":"\r\n            var authorizationUriBuilder = new UriBuilder(_endpoints.AuthorizationEndpoint)\r\n            {\r\n                Query = queryParams.ToQueryString()\r\n            };\r\n\r\n            Uri authorizationUri = authorizationUriBuilder.Uri;\r\n\r\n            // Open the browser at the request URI to start the authorization code grant flow, and\r\n            // intercept the response parameters delivered to the redirect URI.\r\n            IDictionary<string, string> responseParams =\r\n                await browser.GetAuthenticationResponseAsync(authorizationUri, redirectUri, _responseMode, ct);\r\n\r\n            // Check for errors serious enough we should terminate the flow, such as if the state value returned does\r\n            // not match the one we passed. This indicates a badly implemented Authorization Server, or worse, some\r\n            // form of failed MITM or replay attack.\r\n            if (!responseParams.TryGetValue(OAuth2Constants.AuthorizationGrantResponse.StateParameter, out string replyState))\r\n            {\r\n                throw new Trace2OAuth2Exception(_trace2,\r\n                    $\"Missing '{OAuth2Constants.AuthorizationGrantResponse.StateParameter}' in response.\");\r\n            }\r\n            if (!StringComparer.Ordinal.Equals(state, replyState))\r\n            {\r\n                throw new Trace2OAuth2Exception(_trace2,\r\n                    $\"Invalid '{OAuth2Constants.AuthorizationGrantResponse.StateParameter}' in response; does not match the request.\");\r\n            }\r\n\r\n            // We expect to have the auth code in the response otherwise terminate the flow (we failed authentication for some reason)\r\n            if (!responseParams.TryGetValue(OAuth2Constants.AuthorizationGrantResponse.AuthorizationCodeParameter, out string authCode))\r\n            {\r\n                throw new Trace2OAuth2Exception(_trace2,\r\n                    $\"Missing '{OAuth2Constants.AuthorizationGrantResponse.AuthorizationCodeParameter}' in response.\");\r\n            }\r\n\r\n            return new OAuth2AuthorizationCodeResult(authCode, redirectUri, codeVerifier);\r\n        }\r\n\r","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/git-ecosystem/git-credential-manager/blob/e8ce762cd04b4100ae637b5fbf39ef9d0a96561e/src/Core/Authentication/OAuth/OAuth2Client.cs#L163-L199","documentation":"During the OAuth2 authorization code flow, the authorization server must echo back the 'state' parameter that the client sent. GetAuthorizationCodeAsync throws this Trace2OAuth2Exception when the response contains no state parameter at all. A missing state means the response cannot be validated against CSRF/replay, and per the OAuth2 spec the client must abort the flow.","triggerScenarios":"Calling GetAuthorizationCodeAsync and parsing an authorization response (redirect URL / response parameters) that lacks the 'state' entry — e.g. the IdP stripped it, an error redirect omitted it, or the wrong URL was passed as the response.","commonSituations":"Badly implemented or misconfigured authorization server/identity provider; intercepting and forwarding the wrong redirect URL; testing against a stub IdP that does not implement state; an error response from the server that skips state echo.","solutions":["Verify the authorization request URL includes the state parameter and that the full redirect URL (including query/fragment) is passed to the response parser.","Check the identity provider correctly echoes state; fix or replace a custom/stub authorization server.","Inspect whether the response is an OAuth error response (error/error_description) being fed into the success path; handle error responses separately.","Retry the sign-in flow — transient proxies or redirect handlers sometimes mangle the redirect URL."],"exampleFix":"// before\nvar result = client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, verifier,\n    new Uri(\"https://client/callback\")); // response URL missing ?state=...\n// after\nvar responseUrl = new Uri(\"https://client/callback?code=abc&state=\" + Uri.EscapeDataString(state));\nvar result = client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, verifier, responseUrl);","handlingStrategy":"try-catch","validationCode":"var responseParams = ParseResponseParams(callbackUrl);\nif (!responseParams.ContainsKey(\"state\"))\n    throw new InvalidOperationException(\"Authorization response is missing 'state'; aborting flow.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    result = await client.GetAuthorizationCodeAsync(endpoints, clientId, redirectUri, scopes, state, verifier, callbackUrl);\n}\ncatch (Trace2OAuth2Exception ex) when (ex.Message.Contains(\"Missing 'state'\"))\n{\n    // non-compliant IdP or mangled redirect; abort and restart the login flow\n    return AuthFailure.StateMissing;\n}","preventionTips":["Always send state in the authorization request and verify it is echoed before parsing other parameters.","Handle OAuth error redirects (error parameter) separately from success responses.","Test your local redirect listener preserves the full query string.","Use a compliant IdP; treat missing state as a security failure, never proceed."],"tags":["oauth2","csrf","state-validation","authorization-code","security"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"e8ce762cd04b4100ae637b5fbf39ef9d0a96561e","analyzedAt":"2026-09-11T17:15:08.753Z","contentChangedAt":"2026-09-11T17:15:08.753Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}