{"record":{"id":"b035ee3db358e393","repo":"git-ecosystem/git-credential-manager","slug":"oauth2-response-error-from-device-authorization-e","errorCode":null,"errorMessage":"OAuth2 response error (from device authorization endpoint response)","messagePattern":"OAuth2 response error \\(from device authorization endpoint response\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/Core/Authentication/OAuth/OAuth2Client.cs","lineNumber":234,"sourceCode":"            };\r\n\r\n            if (!string.IsNullOrWhiteSpace(scopesStr))\r\n            {\r\n                formData[OAuth2Constants.ScopeParameter] = scopesStr;\r\n            }\r\n\r\n            using (HttpContent requestContent = new FormUrlEncodedContent(formData))\r\n            using (HttpRequestMessage request = CreateRequestMessage(HttpMethod.Post, _endpoints.DeviceAuthorizationEndpoint, requestContent))\r\n            using (HttpResponseMessage response = await _httpClient.SendAsync(request, ct))\r\n            {\r\n                string json = await response.Content.ReadAsStringAsync();\r\n\r\n                if (response.IsSuccessStatusCode && TryDeserializeJson(json, OAuthJsonContext.Default.DeviceAuthorizationEndpointResponseJson, out DeviceAuthorizationEndpointResponseJson jsonObj))\r\n                {\r\n                    return jsonObj.ToResult();\r\n                }\r\n\r\n                throw CreateExceptionFromResponse(json);\r\n            }\r\n        }\r\n\r\n        public async Task<OAuth2TokenResult> GetTokenByAuthorizationCodeAsync(OAuth2AuthorizationCodeResult authorizationCodeResult, CancellationToken ct)\r\n        {\r\n            var label = \"get token by auth code\";\r\n            using IDisposable region = _trace2.CreateRegion(OAuth2Constants.Trace2Category, label);\r\n\r\n            var formData = new Dictionary<string, string>\r\n            {\r\n                [OAuth2Constants.TokenEndpoint.GrantTypeParameter] = OAuth2Constants.TokenEndpoint.AuthorizationCodeGrantType,\r\n                [OAuth2Constants.TokenEndpoint.AuthorizationCodeParameter] = authorizationCodeResult.Code,\r\n                [OAuth2Constants.TokenEndpoint.PkceVerifierParameter] = authorizationCodeResult.CodeVerifier,\r\n                [OAuth2Constants.ClientIdParameter] = _clientId,\r\n                [OAuth2Constants.ClientSecretParameter] = _clientSecret\r\n            };\r\n\r\n            if (authorizationCodeResult.RedirectUri != null)\r","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/git-ecosystem/git-credential-manager/blob/e8ce762cd04b4100ae637b5fbf39ef9d0a96561e/src/Core/Authentication/OAuth/OAuth2Client.cs#L216-L252","documentation":"Thrown by OAuth2Client.GetDeviceCodeAsync when the device authorization endpoint either returns a non-success HTTP status or returns a 2xx body that cannot be deserialized into a DeviceAuthorizationEndpointResponseJson. The library parses the response body with CreateExceptionFromResponse and throws an OAuth2Exception carrying the server's error/error_description if the body is a standard RFC 8628 error JSON, otherwise a generic 'Unknown OAuth error' Trace2OAuth2Exception. It signals that the device-code flow could not even start: no device code, user code, or verification URI was issued.","triggerScenarios":"Calling GetDeviceCodeAsync when the configured DeviceAuthorizationEndpoint URL is wrong or unreachable (404/502), the client_id is unknown to the authorization server (invalid_client), the requested scopes are invalid, the server rejects the client (e.g. device flow disabled for the app), or the endpoint returns HTML/empty/non-JSON that fails TryDeserializeJson despite a 2xx status.","commonSituations":"Misconfigured endpoint base URL (trailing path mistakes after an IdP migration), device authorization grant not enabled on the OAuth app registration, wrong/rotated client_id, a proxy or captive portal returning an HTML error page with a 2xx/4xx status, or an IdP outage returning a non-JSON error page.","solutions":["Inspect the thrown OAuth2Exception.Error / error_description to see the server's actual rejection reason (e.g. invalid_client, unauthorized_client).","Verify the DeviceAuthorizationEndpoint configured on the client matches the IdP's published device authorization URL exactly.","Confirm the OAuth application registration has the device authorization grant enabled and that client_id is correct.","Check requested scopes are valid and granted for the client on the authorization server.","Capture the raw response body (present in the 'Unknown OAuth error' message when the body is not standard error JSON) to identify proxies/CDNs returning non-JSON responses."],"exampleFix":"// before: device flow endpoint guessed/misconfigured\nvar client = new OAuth2Client(endpoints with DeviceAuthorizationEndpoint = new Uri(\"https://login.example.com/device\"));\n// after: use the IdP's documented device authorization endpoint\nendpoints.DeviceAuthorizationEndpoint = new Uri(\"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/devicecode\");\nvar deviceCode = await client.GetDeviceCodeAsync(scopes, ct);","handlingStrategy":"try-catch","validationCode":"// Before calling, validate configuration\nif (endpoints?.DeviceAuthorizationEndpoint is null) throw new InvalidOperationException(\"Configure DeviceAuthorizationEndpoint before GetDeviceCodeAsync\");\nif (string.IsNullOrWhiteSpace(clientId)) throw new InvalidOperationException(\"client_id must be set\");","typeGuard":"// Narrow the exception to inspect the server's error code\nstatic bool TryGetOAuthError(Exception ex, out string error)\n{\n    error = (ex as OAuth2Exception)?.Error;\n    return error is not null;\n}","tryCatchPattern":"try\n{\n    var deviceCode = await client.GetDeviceCodeAsync(scopes, ct);\n}\ncatch (OAuth2Exception ex)\n{\n    logger.LogError(ex, \"Device authorization start failed: {Error} {Description}\", ex.Error, ex.ErrorDescription);\n    throw; // configuration/server problem, not transient\n}","preventionTips":["Validate the device authorization endpoint URL against the IdP's published discovery document at startup.","Enable the device authorization grant on the app registration before shipping device-flow support.","Log the raw response body when the exception message shows 'Unknown OAuth error' to catch proxy/HTML interference early."],"tags":["oauth2","device-flow","http-error-response","authentication"],"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"}