microsoft/aspire · error · InvalidOperationException

Response missing refresh_token.

Error message

Response missing refresh_token.

What it means

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.

Solutions

  1. Verify the POST actually reached Azure Container Registry and not an intercepting proxy (check response content type).
  2. Re-authenticate with a fresh AAD access token; a stale/invalid token can yield an empty exchange result.
  3. Retry the exchange; if reproducible, inspect the raw response body and the ACR service health.
Defensive patterns

Strategy: try-catch

Validate before calling

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.");
if (!doc.RootElement.TryGetProperty("refresh_token", out var t) || t.GetString() is not { Length: > 0 }) throw new InvalidOperationException("ACR exchange response lacks refresh_token.");

Type guard

static bool HasRefreshToken(AcrRefreshTokenResponse? r) => !string.IsNullOrEmpty(r?.RefreshToken);

Try / catch

try { var rt = await ExchangeAadTokenForAcrRefreshTokenAsync(...); } catch (InvalidOperationException ex) when (ex.Message.Contains("refresh_token")) { /* inspect raw response / re-authenticate */ }

Prevention

When it happens

Trigger: Calling ExchangeAadTokenForAcrRefreshTokenAsync when the ACR exchange endpoint returns a 2xx body without a refresh_token field (unexpected contract, proxy interference, or empty body).

Common situations: 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.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/d168521d775316d0. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.Azure/AcrLoginService.cs:186

        // Read response body as string once
        var responseBody = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);

        if (!response.IsSuccessStatusCode)
        {
            var truncatedBody = responseBody.Length <= 1000 ? responseBody : responseBody[..1000] + "…";
            throw new HttpRequestException(
                $"POST /oauth2/exchange failed {(int)response.StatusCode} {response.ReasonPhrase}. Body: {truncatedBody}",
                null,
                response.StatusCode);
        }

        // Deserialize from the string we already read
        var tokenResponse = JsonSerializer.Deserialize<AcrRefreshTokenResponse>(responseBody, s_jsonOptions);

        if (string.IsNullOrEmpty(tokenResponse?.RefreshToken))
        {
            throw new InvalidOperationException($"Response missing refresh_token.");
        }

        return tokenResponse.RefreshToken;
    }
}

View on GitHub (pinned to 25830f84bd)