{"record":{"id":"d168521d775316d0","repo":"microsoft/aspire","slug":"response-missing-refresh-token","errorCode":null,"errorMessage":"Response missing refresh_token.","messagePattern":"Response missing refresh_token\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Azure/AcrLoginService.cs","lineNumber":186,"sourceCode":"\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":168,"sourceCodeEnd":192,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Azure/AcrLoginService.cs#L168-L192","documentation":"After a successful (2xx) /oauth2/exchange response, AcrLoginService deserializes the body and requires a non-empty refresh_token. If the JSON payload is null or lacks RefreshToken, this InvalidOperationException is thrown.","triggerScenarios":"Calling ExchangeAadTokenForAcrRefreshTokenAsync when the ACR exchange endpoint returns a 2xx body without a refresh_token field (unexpected contract, proxy interference, or empty body).","commonSituations":"Corporate proxies/gateways returning 200 HTML pages; ACR endpoint behavioral changes; hitting a non-ACR server that accepts the POST but returns a different schema.","solutions":["Verify the POST actually reached Azure Container Registry and not an intercepting proxy (check response content type).","Re-authenticate with a fresh AAD access token; a stale/invalid token can yield an empty exchange result.","Retry the exchange; if reproducible, inspect the raw response body and the ACR service health."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"using var doc = response.Headers.ContentType?.MediaType?.Contains(\"json\") == true ? JsonDocument.Parse(await response.Content.ReadAsStringAsync(ct)) : throw new InvalidOperationException(\"ACR exchange returned non-JSON response; check proxy settings.\");\nif (!doc.RootElement.TryGetProperty(\"refresh_token\", out var t) || t.GetString() is not { Length: > 0 }) throw new InvalidOperationException(\"ACR exchange response lacks refresh_token.\");","typeGuard":"static bool HasRefreshToken(AcrRefreshTokenResponse? r) => !string.IsNullOrEmpty(r?.RefreshToken);","tryCatchPattern":"try { var rt = await ExchangeAadTokenForAcrRefreshTokenAsync(...); } catch (InvalidOperationException ex) when (ex.Message.Contains(\"refresh_token\")) { /* inspect raw response / re-authenticate */ }","preventionTips":["Verify no proxy rewrites ACR responses (check Content-Type is application/json).","Use a freshly acquired AAD access token for the exchange.","Retry once on empty results before failing."],"tags":["azure","acr","token-exchange","response"],"backgroundTag":"unexpected-response-shape","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"}