{"record":{"id":"084c5b580872e23e","repo":"dotnet/aspnetcore","slug":"invalid-authentication-refresh-response-received","errorCode":null,"errorMessage":"Invalid authentication refresh response received: expected JSON content.","messagePattern":"Invalid authentication refresh response received: expected JSON content\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/SignalR/clients/ts/signalr/src/HttpConnection.ts","lineNumber":440,"sourceCode":"\n        const refreshUrl = this._createRefreshUrl(this._connectionUrl, this._connectionToken);\n        this._logger.log(LogLevel.Debug, `Sending authentication refresh request: ${refreshUrl}.`);\n\n        const request: HttpRequest = {\n            content: \"\",\n            headers: { ...headers, ...this._options.headers },\n            timeout: this._options.timeout,\n            withCredentials: this._options.withCredentials,\n        };\n        this._httpClient.markAuthenticationRefreshRequest(request);\n        const response = await this._httpClient.post(refreshUrl, request);\n\n        if (response.statusCode !== 200) {\n            throw new Error(`Unexpected status code returned from authentication refresh '${response.statusCode}'`);\n        }\n\n        if (typeof response.content !== \"string\") {\n            throw new Error(\"Invalid authentication refresh response received: expected JSON content.\");\n        }\n\n        if (connectionGeneration !== this._connectionGeneration) {\n            return undefined;\n        }\n\n        const refreshResponse = JSON.parse(response.content) as { accessToken?: unknown, tokenLifetimeSeconds?: unknown };\n        if (typeof refreshResponse.accessToken === \"string\" && refreshResponse.accessToken) {\n            // Redirecting servers can return a transport token that should replace the current cached token.\n            this._setTransportAccessToken(refreshResponse.accessToken);\n        } else if (!this._transportAccessTokenFromServer) {\n            // Without a server-provided transport token, reuse the app token that successfully authenticated refresh.\n            const refreshRequestToken = this._httpClient.getRefreshRequestToken(response);\n            if (refreshRequestToken) {\n                this._httpClient.updateCachedToken(refreshRequestToken);\n            }\n        }\n","sourceCodeStart":422,"sourceCodeEnd":458,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/SignalR/clients/ts/signalr/src/HttpConnection.ts#L422-L458","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the server returns a JSON string body for the refresh endpoint with Content-Type application/json.","If you supply a custom httpClient in options, make sure HttpResponse.content is a string for text responses.","Verify no middleware is converting the response to ArrayBuffer before the client parses it."],"exampleFix":"// custom httpClient must return string content for refresh\n// before\nreturn { statusCode: 200, content: new ArrayBuffer(...) };\n\n// after\nreturn { statusCode: 200, content: JSON.stringify({ accessToken, tokenLifetimeSeconds }) };","handlingStrategy":"validation","validationCode":"// if you use a custom httpClient, ensure refresh responses come back as strings\nfunction assertStringContent(response) {\n  if (typeof response.content !== 'string') {\n    response.content = new TextDecoder().decode(response.content);\n  }\n  return response;\n}","typeGuard":"function isStringContent(res: { content: unknown }): res is { content: string } {\n  return typeof res.content === 'string';\n}","tryCatchPattern":"try { await hub.refreshAuthentication(); }\ncatch (e) {\n  if (/expected JSON content/.test(String(e))) {\n    // server or custom httpClient returned non-string - fix the httpClient, don't retry blindly\n    throw new Error('refresh response was not a JSON string; fix server content-type or httpClient');\n  }\n  throw e;\n}","preventionTips":["If you pass options.httpClient, ensure HttpResponse.content is a string for JSON responses.","Server should return Content-Type: application/json with a textual body.","Avoid interceptors that convert response bodies to ArrayBuffer."],"tags":["authentication","refresh","http-client","serialization"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}