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

  1. Ensure the refresh endpoint returns a JSON body like { "accessToken": "...", "tokenLifetimeSeconds": 3600 } with Content-Type: application/json.
  2. If using a custom HttpClient, make sure refresh responses come back with content as a string.
  3. 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

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

Related errors


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)