{"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/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/SignalR/clients/ts/signalr/src/HttpConnection.ts#L422-L458","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"async function assertRefreshBody(url: string, token: string) {\n  const r = await fetch(`${url}/refresh?access=${encodeURIComponent(token)}`, { method: \"POST\" });\n  const text = await r.text();\n  if (typeof text !== \"string\" || text.length === 0) throw new Error(\"Refresh endpoint must return JSON text\");\n  JSON.parse(text); // throws if not JSON\n}","typeGuard":"function isStringBody(content: unknown): content is string {\n  return typeof content === \"string\" && content.length > 0;\n}","tryCatchPattern":"try { await connection.start(); }\ncatch (e) {\n  if (e instanceof Error && /expected JSON content/.test(e.message)) {\n    console.error(\"Refresh endpoint did not return a JSON string body.\");\n  }\n  throw e;\n}","preventionTips":["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 }."],"tags":["authentication","token-refresh","serialization","http"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}