Skip to content

Fix SignalR TS client access token retry when HttpClient throws on 401#67851

Open
Arul1998 wants to merge 1 commit into
dotnet:mainfrom
Arul1998:fix/signalr-ts-client-401-retry
Open

Fix SignalR TS client access token retry when HttpClient throws on 401#67851
Arul1998 wants to merge 1 commit into
dotnet:mainfrom
Arul1998:fix/signalr-ts-client-401-retry

Conversation

@Arul1998

Copy link
Copy Markdown

Fix SignalR TypeScript client access token retry when the HttpClient throws on 401

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Description

In the browser/TypeScript SignalR client, AccessTokenHttpClient.send contains retry logic that is meant to renew the access token via the access token factory and resend the request when a 401 Unauthorized is received. However, the HttpClient implementations (e.g. FetchHttpClient) throw an HttpError for non-2xx responses, so the response.statusCode === 401 check after the await is never reached — the thrown error propagates instead and the token is never renewed.

This notably affects:

  • Server-Sent Events keep-alive pings — when the bearer token expires and the ping gets a 401, the connection closes instead of renewing the token, and
  • LongPolling — a 401 on a reissued poll takes the long close lifecycle instead of renewing the token and continuing to poll.

What changed

AccessTokenHttpClient.send now catches a thrown HttpError with status code 401 (subject to the same allowRetry conditions as the existing logic), renews the access token via the factory, sets the new Authorization header, and resends the request once. The retried request is intentionally not guarded against another 401, so a failing retry surfaces to the caller rather than retrying indefinitely — matching the single-retry semantics of the existing non-throwing path, which is unchanged. The shared renewal logic is extracted into a private _retryWithNewToken helper used by both paths.

Behavior is unchanged for: negotiate requests (never retried), clients without an access token factory, and non-401 errors (all propagate as before).

Testing

Added AccessTokenHttpClient.test.ts with six tests:

  • retries with a new token when the 401 arrives as a response (existing behavior, previously untested)
  • retries with a new token when the 401 arrives as a thrown HttpError (the bug)
  • retries at most once when the retried request also fails with 401
  • does not retry on a thrown non-401 HttpError
  • does not retry when no access token factory is configured
  • does not retry negotiate requests

All SignalR TS client test suites that exercise this code path (AccessTokenHttpClient, LongPollingTransport, ServerSentEventsTransport, HttpClient, HubConnection) pass locally with this change.

Fixes #56494

The HttpClient implementations throw an HttpError for non-2xx responses, so the 401 handling in AccessTokenHttpClient never executed for SSE keep-alive pings and LongPolling requests. Catch a thrown 401, renew the access token via the factory, and retry the request once, matching the existing non-throwing retry behavior.

Fixes dotnet#56494
@Arul1998

Copy link
Copy Markdown
Author

@dotnet-policy-service agree

@gfoidl gfoidl added area-signalr Includes: SignalR clients and servers feature-client-javascript Related to the SignalR JS client labels Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-signalr Includes: SignalR clients and servers feature-client-javascript Related to the SignalR JS client

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SignalR - Browser/TypeScript client - the Server-Sent Event keep alive ping fails to renew the access token if a 401 Unauthorized happens on the ping

2 participants