{"record":{"id":"29bf11ad972ce250","repo":"dotnet/aspnetcore","slug":"authentication-refresh-is-only-supported-with-http","errorCode":null,"errorMessage":"Authentication refresh is only supported with HTTP-based connections.","messagePattern":"Authentication refresh is only supported with HTTP-based connections\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/SignalR/clients/ts/signalr/src/HubConnection.ts","lineNumber":625,"sourceCode":"     */\n    public onreconnected(callback: (connectionId?: string) => void): void {\n        if (callback) {\n            this._reconnectedCallbacks.push(callback);\n        }\n    }\n\n    /** Refreshes the authentication state for this connection.\n     *\n     * @returns A Promise that resolves with the new server-reported token lifetime in seconds, or undefined when the server does not report one.\n     */\n    public async refreshAuthentication(): Promise<number | undefined> {\n        if (this._connectionState !== HubConnectionState.Connected) {\n            throw new Error(\"Cannot refresh authentication when the connection is not active.\");\n        }\n\n        const authenticationRefreshFeature = this.connection.features.authenticationRefresh as IAuthenticationRefreshFeature | undefined;\n        if (!authenticationRefreshFeature) {\n            throw new Error(\"Authentication refresh is only supported with HTTP-based connections.\");\n        }\n\n        let newTokenLifetimeInSeconds: number | undefined;\n        try {\n            newTokenLifetimeInSeconds = await authenticationRefreshFeature.refreshAuthentication();\n        } catch (e) {\n            await this._invokeAuthenticationRefreshFailed(e);\n            throw e;\n        }\n\n        if (this._connectionState === HubConnectionState.Connected &&\n            this.connection.features.authenticationRefresh === authenticationRefreshFeature &&\n            this._isAutoAuthenticationRefreshEnabled() &&\n            isValidAuthenticationTokenLifetime(newTokenLifetimeInSeconds)) {\n            this._scheduleAuthenticationRefresh(newTokenLifetimeInSeconds);\n        }\n\n        await this._invokeAuthenticationRefreshed(newTokenLifetimeInSeconds);","sourceCodeStart":607,"sourceCodeEnd":643,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/SignalR/clients/ts/signalr/src/HubConnection.ts#L607-L643","documentation":"Thrown at HubConnection.ts:625 when `refreshAuthentication()` is called on a connection whose `features.authenticationRefresh` is not set. The authentication refresh feature is only configured by HttpConnection after a successful negotiate (see _configureAuthenticationRefresh at HttpConnection.ts:402); a non-HTTP connection (custom IConnection, or skipNegotiate which skips negotiate) does not set it.","triggerScenarios":"Using a custom IConnection implementation that doesn't expose the authenticationRefresh feature; constructing HttpConnection with `skipNegotiation: true` (which bypasses _configureAuthenticationRefresh); calling refreshAuthentication on a mock connection in tests.","commonSituations":"Injecting a custom IConnection (e.g. a mock or a non-HTTP transport) but expecting refresh to work; using skipNegotiation with WebSockets and still wanting server-side token refresh; tests that build a HubConnection over a fake connection.","solutions":["If you need refresh, use a standard HTTP-based HttpConnection without skipNegotiation so negotiate runs and sets the feature.","For a custom IConnection, expose `features.authenticationRefresh = { initialTokenLifetimeInSeconds, refreshAuthentication }`.","If you skipped negotiate, acquire app tokens via the accessTokenFactory instead of server refresh."],"exampleFix":"// before\nnew HubConnectionBuilder().withUrl(url, { skipNegotiation: true, transport: HttpTransportType.WebSockets }).build();\nawait hub.refreshAuthentication(); // throws - feature not set\n\n// after - use accessTokenFactory for app-side tokens\nnew HubConnectionBuilder().withUrl(url, { accessTokenFactory: () => getJwt() }).build();","handlingStrategy":"type-guard","validationCode":"function hasRefreshFeature(conn) {\n  return !!conn && !!conn.features && typeof conn.features.authenticationRefresh?.refreshAuthentication === 'function';\n}\nif (hasRefreshFeature(hub.connection)) await hub.refreshAuthentication();\nelse { await hub.stop(); await hub.start(); }","typeGuard":"function supportsAuthRefresh(hub: HubConnection): boolean {\n  const f = (hub as any).connection?.features?.authenticationRefresh;\n  return !!f && typeof f.refreshAuthentication === 'function';\n}","tryCatchPattern":"try { await hub.refreshAuthentication(); }\ncatch (e) {\n  if (/HTTP-based connections/.test(String(e))) {\n    // no server-side refresh; do a full reconnect using accessTokenFactory\n    await hub.stop(); await hub.start();\n  } else throw e;\n}","preventionTips":["Use a standard HttpConnection without skipNegotiation if you need server-side refresh.","For custom IConnection implementations, expose the authenticationRefresh feature.","If you skipped negotiate, use accessTokenFactory to rotate tokens client-side."],"tags":["authentication","refresh","transport","custom-connection"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}