dotnet/aspnetcore · error · Error
Invalid authentication refresh response received: expected…
Error message
Invalid authentication refresh response received: expected JSON content.
What it means
After a 200 refresh response, the client requires response.content to be a string so it can JSON.parse the token payload. If the content is not a string (e.g. an ArrayBuffer because responseType was set to arraybuffer, or undefined because the body was empty), the client cannot parse the token and throws.
Solutions
- Ensure the refresh endpoint returns a JSON body like { "accessToken": "...", "tokenLifetimeSeconds": 3600 } with Content-Type: application/json.
- If using a custom HttpClient, make sure refresh responses come back with content as a string.
- Verify the server's refresh controller actually writes a JSON response (not NoContent).
Defensive patterns
Strategy: validation
Validate before calling
async function assertRefreshBody(url: string, token: string) {
const r = await fetch(`${url}/refresh?access=${encodeURIComponent(token)}`, { method: "POST" });
const text = await r.text();
if (typeof text !== "string" || text.length === 0) throw new Error("Refresh endpoint must return JSON text");
JSON.parse(text); // throws if not JSON
} Type guard
function isStringBody(content: unknown): content is string {
return typeof content === "string" && content.length > 0;
} Try / catch
try { await connection.start(); }
catch (e) {
if (e instanceof Error && /expected JSON content/.test(e.message)) {
console.error("Refresh endpoint did not return a JSON string body.");
}
throw e;
} Prevention
- Ensure the refresh endpoint returns JSON with Content-Type: application/json and a non-empty body.
- If using a custom HttpClient, ensure refresh responses return content as a string.
- Verify the refresh controller writes { accessToken, tokenLifetimeSeconds }.
When it happens
Trigger: The refresh endpoint returned 200 but the response was deserialized as a non-string (ArrayBuffer when responseType='arraybuffer'), or the body was empty/undefined. Also possible if a custom HttpClient returns an HttpResponse whose content is not a string for the refresh call.
Common situations: Server refresh endpoint returns 200 with no body. Custom HttpClient forces arraybuffer responseType for all calls. Reverse proxy stripping the body. Server returns the token in a header instead of the body.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unexpected status code returned from authentication refresh
- errorMessage || response.statusText
- The server responded with status
- Authentication refresh is only supported with HTTP-based…
- Authentication refreshBeforeExpirationInMilliseconds must…
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/084c5b580872e23e.
Report an issue: GitHub.
Appendix: source
Thrown at src/SignalR/clients/ts/signalr/src/HttpConnection.ts:440
const refreshUrl = this._createRefreshUrl(this._connectionUrl, this._connectionToken);
this._logger.log(LogLevel.Debug, `Sending authentication refresh request: ${refreshUrl}.`);
const request: HttpRequest = {
content: "",
headers: { ...headers, ...this._options.headers },
timeout: this._options.timeout,
withCredentials: this._options.withCredentials,
};
this._httpClient.markAuthenticationRefreshRequest(request);
const response = await this._httpClient.post(refreshUrl, request);
if (response.statusCode !== 200) {
throw new Error(`Unexpected status code returned from authentication refresh '${response.statusCode}'`);
}
if (typeof response.content !== "string") {
throw new Error("Invalid authentication refresh response received: expected JSON content.");
}
if (connectionGeneration !== this._connectionGeneration) {
return undefined;
}
const refreshResponse = JSON.parse(response.content) as { accessToken?: unknown, tokenLifetimeSeconds?: unknown };
if (typeof refreshResponse.accessToken === "string" && refreshResponse.accessToken) {
// Redirecting servers can return a transport token that should replace the current cached token.
this._setTransportAccessToken(refreshResponse.accessToken);
} else if (!this._transportAccessTokenFromServer) {
// Without a server-provided transport token, reuse the app token that successfully authenticated refresh.
const refreshRequestToken = this._httpClient.getRefreshRequestToken(response);
if (refreshRequestToken) {
this._httpClient.updateCachedToken(refreshRequestToken);
}
}
View on GitHub (pinned to 3600ca084e)