Skip to content

Use SignalR authentication refresh for Blazor Identity - #68663

Open
javiercn wants to merge 5 commits into
mainfrom
javiercn/simplify-blazor-auth-refresh
Open

Use SignalR authentication refresh for Blazor Identity#68663
javiercn wants to merge 5 commits into
mainfrom
javiercn/simplify-blazor-auth-refresh

Conversation

@javiercn

@javiercn javiercn commented Aug 20, 2026

Copy link
Copy Markdown
Member

Summary

This PR is stacked on #68676 and uses its server-enforced SignalR authentication expiration mechanism to replace the Blazor Identity template's IdentityRevalidatingAuthenticationStateProvider.

  • configures MaximumAuthenticationExpiration to 40 minutes for generated Individual-auth server apps
  • enables CloseOnAuthenticationExpiration, so a connection that does not refresh is closed at the enforced deadline
  • relies on SignalR's existing five-minute refresh lead, causing the client to reauthenticate at approximately 35 minutes
  • relies on the Identity cookie handler to run its normal security-stamp validation during reauthentication
  • accepts anonymous refreshed principals so sign-out propagates into the existing circuit
  • composes existing ConfigureConnection configuration
  • preserves stricter application-provided maximum expirations while preventing a weaker value from replacing the template policy
  • removes IdentityRevalidatingAuthenticationStateProvider and its template registration

Does the 40-minute interval drift?

No. MaximumAuthenticationExpiration is applied relative to each successful authentication refresh. SignalR refreshes approximately five minutes before that deadline, so the Identity cookie handler reauthenticates at roughly 35 minutes—after its default 30-minute security-stamp validation interval has elapsed.

A successful stamp validation renews the principal/cookie, and the next 40-minute maximum is measured from that refresh. Normal timer and request latency can shift an individual refresh slightly, but the offset does not accumulate across successful refresh cycles.

If refresh does not succeed, the connection expiration is not advanced and the server closes the connection at the existing deadline.

Invariants

  • the effective authentication expiration is the earlier of the ticket expiration and the 40-minute maximum
  • the client refreshes approximately five minutes before that expiration
  • successful refresh advances the connection expiration by at most 40 minutes
  • Identity's cookie handler performs security-stamp validation during reauthentication
  • a refreshed anonymous principal updates the circuit authentication state
  • a missed refresh causes the server to close the connection at expiration
  • successful refresh does not replace the SignalR connection

Follow-up

Identity clock-skew behavior for periodic reauthentication is tracked in #68694.

Validation

  • Blazor CircuitManager authentication-refresh configuration test
  • Components E2E test covering automatic authenticated-to-anonymous transition without reconnecting
  • SignalR and Blazor JavaScript debug builds
  • generated Individual-auth template package build and publish for the top-level Program.cs shape; local launch is blocked by the unavailable 11.0.0-dev shared runtime in the template test harness

Copilot AI lite review requested due to automatic review settings August 20, 2026 13:53
@javiercn
javiercn requested review from a team, BrennanConroy and halter73 as code owners August 20, 2026 13:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Prototype wiring for Blazor Server + Identity to rely on SignalR authentication refresh (and a 30-minute client-driven refresh loop) instead of the template’s IdentityRevalidatingAuthenticationStateProvider, with accompanying tests and template baseline updates.

Changes:

  • SignalR TS client: coalesce concurrent refreshAuthentication() calls and ensure callbacks can trigger follow-up refreshes.
  • Blazor Web.JS: add a 30-minute periodic refresh loop in CircuitManager, plus tests and E2E coverage for authenticated→anonymous transition without reconnect.
  • Blazor Identity template: remove IdentityRevalidatingAuthenticationStateProvider and its registration/baseline entries.

Reviewed changes

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

Show a summary per file
File Description
src/SignalR/clients/ts/signalr/tests/HubConnection.test.ts Adds coverage for refresh coalescing and nested refresh scenarios.
src/SignalR/clients/ts/signalr/src/HubConnection.ts Implements coalescing of concurrent refresh operations and completion bookkeeping.
src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts Adds a fixed-interval (30 min) authentication refresh timer and rearm/cleanup logic.
src/Components/Web.JS/test/Platform/Circuits/CircuitManagerAuthenticationRefresh.test.ts Adds unit tests for refresh configuration ordering and 30-minute refresh cadence.
src/Components/test/testassets/Components.TestServer/Pages/_ServerHost.cshtml Adds query-driven hooks to capture/accelerate auth refresh behavior for E2E.
src/Components/test/E2ETest/ServerExecutionTests/ServerAuthTest.cs Adds E2E test for automatic refresh updating circuit auth state without reconnect.
src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/* Removes template provider and DI registration related to identity revalidation.
src/ProjectTemplates/test/Templates.Tests/template-baselines.json Updates template baselines to reflect removed provider file.
Suppressed comments (1)

src/Components/Web.JS/test/Platform/Circuits/CircuitManagerAuthenticationRefresh.test.ts:76

  • The mocked HubConnection used in this test doesn't implement stop(), but CircuitManager.dispose() calls this._connection?.stop(). Add a stop stub so the test doesn't fail with TypeError: connection.stop is not a function.
    const connection = {
      on: jest.fn(),
      onclose: jest.fn(),
      start: () => Promise.resolve(),
      state: HubConnectionState.Connected,
      refreshAuthentication,
    } as unknown as HubConnection;

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +149 to +152
.withAuthenticationRefresh({
onAuthenticationRefreshed: context => this.scheduleAuthenticationRefresh(context.connection),
onAuthenticationRefreshFailed: context => this.scheduleAuthenticationRefresh(context.connection),
});
Comment on lines 30 to 35
const connection = {
on: jest.fn(),
onclose: jest.fn(),
start: () => Promise.resolve(),
state: HubConnectionState.Connected,
} as unknown as HubConnection;
@BrennanConroy

Copy link
Copy Markdown
Member

#68676 adds MaximumAuthenticationExpiration which should give the 30 minute behavior IdentityRevalidatingAuthenticationStateProvider used to have.

@javiercn
javiercn force-pushed the javiercn/simplify-blazor-auth-refresh branch from e74ed37 to 2b57f7b Compare August 21, 2026 11:35
@javiercn
javiercn requested a review from SamMonoRT as a code owner August 21, 2026 11:35
@javiercn
javiercn changed the base branch from main to brennanconroy-signalr-auth-refresh-apis August 21, 2026 11:36
// security stamp validation interval has elapsed. Update this value if that interval is customized.
private static readonly TimeSpan s_maximumAuthenticationExpiration = TimeSpan.FromMinutes(40);

public static void ConfigureIdentityAuthenticationRefresh(this ServerComponentsEndpointOptions options)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This only composes if ConfigureConnection was assigned before the call. Assigning it after, which is the natural edit to make in that lambda in Program.cs, silently drops the cap and CloseOnAuthenticationExpiration and auth just quietly goes stale. Is composing worth much if it only covers one of the two orders?

I think we probably shouldn't bother even trying to run any existing options.ConfigureConnection callback. It's templated code, so people are free to modify ConfigureIdentityAuthenticationRefresh themselves if they want to merge their logic with the clamping logic.

Base automatically changed from brennanconroy-signalr-auth-refresh-apis to main August 21, 2026 18:49
@BrennanConroy
BrennanConroy force-pushed the javiercn/simplify-blazor-auth-refresh branch from 784ca35 to 8ac197d Compare August 21, 2026 18:49
@javiercn

Copy link
Copy Markdown
Member Author

/azp run aspnetcore-ci

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@javiercn
javiercn force-pushed the javiercn/simplify-blazor-auth-refresh branch from 8ac197d to 5aaa56b Compare August 21, 2026 19:43
@BrennanConroy

Copy link
Copy Markdown
Member

/ba-g looks like an unrelated flaky test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants