Skip to content

Conversation

daniloneto
Copy link
Contributor

fix(SignalR): retry access token refresh on 401 in TS client

  • 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.

Summary of the changes (Less than 80 chars)

Description

  • Handle both HttpError.status and HttpResponse.statusCode
  • Normalize string "401" to number before comparison
  • Retry exactly once using _refreshTokenAndRetry
  • Skip retry when token factory returns empty
  • Preserve existing behavior for non-401 errors
  • Add unit tests for 401 response, HttpError, string "401", no-retry cases

Fixes #56494

…ient

- Handle both HttpError.status and HttpResponse.statusCode
- Normalize string "401" to number before comparison
- Retry exactly once using _refreshTokenAndRetry
- Skip retry when token factory returns empty
- Preserve existing behavior for non-401 errors
- Add unit tests for 401 response, HttpError, string "401", no-retry cases

Fixes dotnet#56494
@Copilot Copilot AI review requested due to automatic review settings September 19, 2025 14:49
@github-actions github-actions bot added the area-signalr Includes: SignalR clients and servers label Sep 19, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Sep 19, 2025
Copy link
Contributor

Thanks for your PR, @@daniloneto. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a bug in the SignalR TypeScript client where access token refresh wasn't being retried properly on 401 errors. The fix handles both HTTP response objects and thrown errors, normalizes string status codes to numbers, and ensures exactly one retry attempt when the access token factory is available.

  • Adds robust 401 error detection for both HttpResponse and HttpError cases
  • Normalizes string "401" status codes to number format for consistent comparison
  • Implements exactly one retry attempt with proper token refresh logic

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/SignalR/clients/ts/signalr/src/AccessTokenHttpClient.ts Core implementation with helper functions for status normalization and retry logic
src/SignalR/clients/ts/signalr/tests/AccessTokenHttpClient.test.ts Comprehensive test coverage for all retry scenarios including edge cases

@daniloneto
Copy link
Contributor Author

@dotnet-policy-service agree

…esends; shrink bundle

fix(SignalR): ensure access token factory is always called and improve error handling in tests
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

if (!t) {
this._accessToken = undefined;
if (request.headers) delete (request.headers as any).Authorization;
if (request.abortSignal) return this._innerClient.send(request);
Copy link
Preview

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition request.abortSignal checks for the existence of the signal, not if it's been aborted. This should likely check request.abortSignal?.aborted to determine if the request was cancelled, or this logic may be incorrect for the intended behavior.

Suggested change
if (request.abortSignal) return this._innerClient.send(request);
if (request.abortSignal?.aborted) return this._innerClient.send(request);

Copilot uses AI. Check for mistakes.

}
}
const h = request.headers || (request.headers = {});
if (this._accessToken) h.Authorization = 'Bearer ' + this._accessToken; else if (this._accessTokenFactory) delete h.Authorization;
Copy link
Preview

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This complex conditional logic should be split into multiple lines for better readability. Consider using separate if-else blocks to make the authorization header logic clearer.

Suggested change
if (this._accessToken) h.Authorization = 'Bearer ' + this._accessToken; else if (this._accessTokenFactory) delete h.Authorization;
if (this._accessToken) {
h.Authorization = 'Bearer ' + this._accessToken;
} else if (this._accessTokenFactory) {
delete h.Authorization;
}

Copilot uses AI. Check for mistakes.

});

const request: HttpRequest = { method: "GET", url: "http://example.com/resource" };
const resp = await client.send(request); // send path with existing logic; allowRetry=false triggered by initial token acquisition above
Copy link
Preview

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment mentions 'allowRetry=false triggered by initial token acquisition above' but there's no token acquisition visible in this test method above this line. The comment appears to be inaccurate or misleading.

Suggested change
const resp = await client.send(request); // send path with existing logic; allowRetry=false triggered by initial token acquisition above
const resp = await client.send(request); // Verifies that allowRetry=false prevents retry on initial token acquisition (401 response).

Copilot uses AI. Check for mistakes.

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 community-contribution Indicates that the PR has been added by a community member
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
1 participant