dotnet/aspnetcore · error · Error

Invalid authentication refresh response received: expected J

Error message

Invalid authentication refresh response received: expected JSON content.

What it means

Thrown at HttpConnection.ts:440 when the refresh endpoint returned HTTP 200 but `response.content` is not a string, so it cannot be JSON.parse'd. The refresh handler expects a textual JSON body; a non-string body (e.g. an ArrayBuffer when the client was configured for binary, or an empty body) is unusable.

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 294cab2f9b)

Solutions

  1. Ensure the server returns a JSON string body for the refresh endpoint with Content-Type application/json.
  2. If you supply a custom httpClient in options, make sure HttpResponse.content is a string for text responses.
  3. Verify no middleware is converting the response to ArrayBuffer before the client parses it.

Example fix

// custom httpClient must return string content for refresh
// before
return { statusCode: 200, content: new ArrayBuffer(...) };

// after
return { statusCode: 200, content: JSON.stringify({ accessToken, tokenLifetimeSeconds }) };
Defensive patterns

Strategy: validation

Validate before calling

// if you use a custom httpClient, ensure refresh responses come back as strings
function assertStringContent(response) {
  if (typeof response.content !== 'string') {
    response.content = new TextDecoder().decode(response.content);
  }
  return response;
}

Type guard

function isStringContent(res: { content: unknown }): res is { content: string } {
  return typeof res.content === 'string';
}

Try / catch

try { await hub.refreshAuthentication(); }
catch (e) {
  if (/expected JSON content/.test(String(e))) {
    // server or custom httpClient returned non-string - fix the httpClient, don't retry blindly
    throw new Error('refresh response was not a JSON string; fix server content-type or httpClient');
  }
  throw e;
}

Prevention

When it happens

Trigger: A custom HttpClient that resolves responses with content as ArrayBuffer or non-string types; a server returning a binary/empty body for the refresh endpoint; a response transformer that strips the string content. The literal check `typeof response.content !== "string"` fails.

Common situations: Wrapping or replacing the DefaultHttpClient with one that returns binary content; server middleware that compresses or alters content type without preserving the body; an interceptor that returns `{ content: null }` on certain paths.

Understand the failure class

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/084c5b580872e23e. Report an issue: GitHub.