{"record":{"id":"3fec4d7856b6fcfa","repo":"microsoft/aspire","slug":"post-oauth2-exchange-failed-int-response-statuscode-response","errorCode":null,"errorMessage":"POST /oauth2/exchange failed {(int)response.StatusCode} {response.ReasonPhrase}. Body: {truncatedBody}","messagePattern":"POST /oauth2/exchange failed (.+?) (.+?)\\. Body: (.+?)","errorType":"exception","errorClass":"HttpRequestException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Azure/AcrLoginService.cs","lineNumber":175,"sourceCode":"\n        var formData = new Dictionary<string, string>\n        {\n            [\"grant_type\"] = \"access_token\",\n            [\"service\"] = registryEndpoint,\n            [\"tenant\"] = tenantId,\n            [\"access_token\"] = aadAccessToken\n        };\n\n        using var content = new FormUrlEncodedContent(formData);\n        var response = await httpClient.PostAsync(exchangeUrl, content, cancellationToken).ConfigureAwait(false);\n\n        // Read response body as string once\n        var responseBody = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);\n\n        if (!response.IsSuccessStatusCode)\n        {\n            var truncatedBody = responseBody.Length <= 1000 ? responseBody : responseBody[..1000] + \"…\";\n            throw new HttpRequestException(\n                $\"POST /oauth2/exchange failed {(int)response.StatusCode} {response.ReasonPhrase}. Body: {truncatedBody}\",\n                null,\n                response.StatusCode);\n        }\n\n        // Deserialize from the string we already read\n        var tokenResponse = JsonSerializer.Deserialize<AcrRefreshTokenResponse>(responseBody, s_jsonOptions);\n\n        if (string.IsNullOrEmpty(tokenResponse?.RefreshToken))\n        {\n            throw new InvalidOperationException($\"Response missing refresh_token.\");\n        }\n\n        return tokenResponse.RefreshToken;\n    }\n}\n","sourceCodeStart":157,"sourceCodeEnd":192,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Azure/AcrLoginService.cs#L157-L192","documentation":"AcrLoginService exchanges an AAD token for an ACR refresh token by POSTing to /oauth2/exchange. When the registry service returns a non-success status, the response body (truncated to 1000 chars) is wrapped in an HttpRequestException carrying the status code.","triggerScenarios":"Calling refreshToken -> ExchangeAadTokenForAcrRefreshTokenAsync when Azure Container Registry rejects the token exchange: expired/insufficient-scope AAD token, wrong registry endpoint, or registry-side auth errors (401/403/429/5xx).","commonSituations":"Not logged in with az login or the identity lacks ACR pull permissions; wrong registry name in the endpoint URL; transient AAD/ACR outages; using managed identity in an environment where it isn't available.","solutions":["Run 'az login' (or ensure the workload identity/managed identity is valid) and confirm the identity has ACR pull access on the registry.","Check the registry endpoint/host is correct for the ACR instance.","Retry on transient status codes (429, 5xx) with backoff; the exception exposes the StatusCode for this.","Inspect the included response Body for the service-side error detail."],"exampleFix":"try\n{\n    var refreshToken = await acrLoginService.RefreshTokenAsync(...);\n}\ncatch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)\n{\n    // re-authenticate (az login / acquire new AAD token) and retry\n}","handlingStrategy":"retry","validationCode":"// Before calling: ensure credentials exist\nif (string.IsNullOrEmpty(aadToken)) throw new InvalidOperationException(\"Acquire an AAD token via az login or DefaultAzureCredential first.\");","typeGuard":null,"tryCatchPattern":"try { return await RefreshTokenAsync(ct); }\ncatch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)\n{ /* re-authenticate */ throw; }\ncatch (HttpRequestException ex) when ((int?)ex.StatusCode is 429 or >= 500) { await Task.Delay(TimeSpan.FromSeconds(2), ct); /* retry */ throw; }","preventionTips":["Ensure a valid az login / managed identity before docker/ACR operations.","Grant the identity AcrPull on the registry.","Implement backoff retry for 429/5xx.","Log the exception's Body snippet for the service error detail."],"tags":["azure","acr","http","token-exchange","docker"],"backgroundTag":"http-error-response","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}