Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 175 additions & 1 deletion aspnetcore/signalr/authn-and-authz.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ author: wadepickett
description: Learn how to use authentication and authorization in your ASP.NET Core apps with SignalR, and compare the process for using cookies versus bearer tokens.
monikerRange: '>= aspnetcore-3.1'
ms.author: wpickett
ms.date: 07/29/2026
ms.date: 08/25/2026
uid: signalr/authn-and-authz
---

Expand Down Expand Up @@ -45,6 +45,16 @@ To enforce updated authorization on an active connection, take one of the follow
* Close affected connections so that clients reconnect and reauthenticate. For bearer token authentication, the [CloseOnAuthenticationExpiration](xref:signalr/configuration#configure-advanced-http-options) option closes connections when the authentication token expires.
* Perform authorization checks in hub methods against current data, such as the user's current roles or claims from a data store, instead of relying only on the cached principal.

:::moniker-end

:::moniker range=">= aspnetcore-11.0"

In .NET 11 and later, a client can refresh the credentials for an active connection without reconnecting. When the client presents an updated token, the server re-authenticates it and replaces the cached `Context.User` in place, so later hub method invocations authorize against the refreshed roles and claims. The refreshed principal must map to the same SignalR user, so a refresh updates roles and claims but doesn't change the connection's user identity or routing. For more information, see [Authentication refresh](#authentication-refresh).

:::moniker-end

:::moniker range=">= aspnetcore-6.0"

### Cookie authentication

In a browser-based app, cookie authentication allows existing user credentials to automatically flow to SignalR connections. When the browser client is used, no extra configuration is needed. If the user is signed in to an app, the SignalR connection automatically inherits this authentication.
Expand Down Expand Up @@ -104,6 +114,170 @@ Register the service after adding services for authentication (with the <xref:Mi

[!code-csharp[](authn-and-authz/6.0sample/SignalRAuthenticationSample/Program.cs?name=snippet_i&highlight=7-11)]

:::moniker-end

:::moniker range=">= aspnetcore-11.0"

### Authentication refresh

A SignalR connection can outlive the access token that established it. When [CloseOnAuthenticationExpiration](xref:signalr/configuration#configure-advanced-http-options) is enabled, the server closes the connection after the token expires, and the client must reconnect to continue. Messages sent during the gap are missed, and group and user routing are disrupted until the client reconnects.

Authentication refresh, available in .NET 11 and later, lets a client update the credentials for an active connection without reconnecting. The server re-authenticates the refresh request through the normal endpoint authorization pipeline and replaces the connection's <xref:System.Security.Claims.ClaimsPrincipal> in place, as long as the refreshed principal maps to the same SignalR user.

#### Enable authentication refresh on the server

Enable authentication refresh in the hub's `MapHub` options by setting `EnableAuthenticationRefresh` to `true`. Enable it together with `CloseOnAuthenticationExpiration` so that a connection whose token expires without being refreshed in time is closed, rather than left open with stale credentials:

```csharp
app.MapHub<ChatHub>("/chat", options =>
{
options.CloseOnAuthenticationExpiration = true;
options.EnableAuthenticationRefresh = true;
});
```

When authentication refresh is enabled and the authentication ticket has an expiration, the negotiate response reports the remaining token lifetime so the client can schedule refreshes.

To inspect or reject a refresh, set the `OnAuthenticationRefresh` callback. It runs after the refresh request is authenticated but before the connection's user is replaced. Return `false` to reject the refresh, in which case the endpoint responds with an HTTP 403 status code and the connection keeps its current user. The callback is an additional check on top of the built-in verification that the refreshed principal maps to the same SignalR user. It can reject a refresh, but it can't approve one that fails the built-in check:

```csharp
app.MapHub<ChatHub>("/chat", options =>
{
options.CloseOnAuthenticationExpiration = true;
options.EnableAuthenticationRefresh = true;
options.OnAuthenticationRefresh = context =>
{
if (!context.NewUser.HasClaim("tenant", "contoso"))
{
return Task.FromResult(false);
}

return Task.FromResult(true);
};
});
```

The refreshed principal must map to the same SignalR user as the connection. If it maps to a different user ID, the refresh is rejected: the endpoint responds with an HTTP 403 status code, and the connection keeps its current user and stays connected. A refresh never changes the connection's `Context.UserIdentifier` or reroutes messages sent with `Clients.User`, even for a successful refresh. The routing identifier is fixed when the connection starts. To change it, reconnect the client.

To bound how far a refresh can extend a connection's authentication expiration, set `MaximumAuthenticationExpiration`. The refreshed expiration is capped to at most this amount of time from the current time, even when the token reports a longer lifetime. This cap applies whenever authentication refresh is enabled, including when the token doesn't set an expiration of its own. In that case, the cap gives the connection a known expiration, so the negotiate response reports a token lifetime and the client can schedule automatic refreshes. The value must be greater than zero and doesn't apply to Windows authentication, which is never tracked or refreshed.

#### Refresh authentication from the .NET client

The .NET client refreshes credentials using the `AccessTokenProvider` configured on the connection. Each refresh calls `AccessTokenProvider` to fetch a fresh access token rather than reusing the token cached when the connection started.

To refresh explicitly, call `RefreshAuthenticationAsync`, which returns the new token lifetime reported by the server:

```csharp
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chat", options =>
{
options.AccessTokenProvider = GetAccessTokenAsync;
})
.Build();

await connection.StartAsync();

TimeSpan? newLifetime = await connection.RefreshAuthenticationAsync();
```

To refresh automatically before the token expires, call `WithAuthenticationRefresh` and configure `AuthenticationRefreshOptions`:

```csharp
var connection = new HubConnectionBuilder()
.WithUrl("https://example.com/chat", options =>
{
options.AccessTokenProvider = GetAccessTokenAsync;
})
.WithAuthenticationRefresh(options =>
{
options.RefreshBeforeExpiration = TimeSpan.FromMinutes(2);
})
.Build();
```

`AuthenticationRefreshOptions` provides the following settings:

* `EnableAutoRefresh`: Enables automatic refresh before the token expires. Defaults to `true`. The client only schedules a refresh when the server reports a token lifetime. If the server doesn't report a lifetime, no automatic refresh is scheduled, and `RefreshAuthenticationAsync` can still be called manually.
* `RefreshBeforeExpiration`: How far ahead of the reported expiration to refresh. Defaults to five minutes.

To observe refreshes, handle the `AuthenticationRefreshed` and `AuthenticationRefreshFailed` events on the connection. Both automatic and manual refreshes raise these events:

```csharp
connection.AuthenticationRefreshed += context =>
{
Console.WriteLine(
$"Authentication refreshed. New lifetime: {context.NewTokenLifetime}");
return Task.CompletedTask;
};

connection.AuthenticationRefreshFailed += context =>
{
Console.WriteLine(
$"Authentication refresh failed: {context.Exception}");
return Task.CompletedTask;
};
```

#### Refresh authentication from the JavaScript client

The JavaScript client refreshes credentials using the `accessTokenFactory` configured on the connection. Each refresh calls `accessTokenFactory` to fetch a fresh access token.

To refresh explicitly, call `refreshAuthentication`, which resolves with the new token lifetime in seconds reported by the server:

```javascript
const newLifetimeInSeconds = await connection.refreshAuthentication();
```

To refresh automatically before the token expires, call `withAuthenticationRefresh` and optionally configure `IAuthenticationRefreshOptions`. Handle refresh results with `onAuthenticationRefreshed` and `onAuthenticationRefreshFailed`:

```javascript
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chat", {
accessTokenFactory: () => getAccessToken()
})
.withAuthenticationRefresh({
refreshBeforeExpirationInMilliseconds: 120000
})
.build();

connection.onAuthenticationRefreshed(context => {
console.log(
`Authentication refreshed. New lifetime: ${context.newTokenLifetimeInSeconds}`);
});

connection.onAuthenticationRefreshFailed(context => {
console.log(`Authentication refresh failed: ${context.error}`);
});
```

`IAuthenticationRefreshOptions` provides the following settings:

* `enableAutoRefresh`: Enables automatic refresh before the token expires. Defaults to `true`. As with the .NET client, automatic refresh is only scheduled when the server reports a token lifetime.
* `refreshBeforeExpirationInMilliseconds`: How far ahead of the reported expiration to refresh, in milliseconds. Defaults to 300,000 (five minutes).

#### React to a refresh in the hub

Override `OnAuthenticationRefreshedAsync` in the hub to run code after the refreshed principal is applied to the connection. `Context.User` reflects the refreshed principal. As described earlier, `Context.UserIdentifier` and SignalR user routing don't change on a refresh:

```csharp
public class ChatHub : Hub
{
public override Task OnAuthenticationRefreshedAsync()
{
return Clients.Caller.SendAsync(
"AuthenticationRefreshed", Context.UserIdentifier);
}
}
```

A hub method that's already running keeps the `Context.User` it started with. Later invocations observe the refreshed `Context.User`. For more information about how SignalR caches the authenticated user, see [User and role changes during the connection lifetime](#user-and-role-changes-during-the-connection-lifetime).

Authentication refresh requires a connection that negotiated protocol version 1 or later. Automatic refresh is scheduled only when the server reports a token lifetime, which typically comes from an authentication scheme that sets an expiration, such as bearer tokens. Windows authentication doesn't report an expiration and isn't tracked or refreshed by this feature.

:::moniker-end

:::moniker range=">= aspnetcore-6.0"

### Cookies versus bearer tokens

Cookies are specific to browsers. Sending them from other kinds of clients adds complexity compared to sending bearer tokens. Cookie authentication isn't recommended unless the app only needs to authenticate users from the browser client. Bearer token authentication is the recommended approach when using clients other than the browser client.
Expand Down
3 changes: 2 additions & 1 deletion aspnetcore/signalr/client-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: ASP.NET Core SignalR clients
author: wadepickett
description: Learn which features are supported by the various ASP.NET Core SignalR clients.
ms.author: wpickett
ms.date: 11/12/2019
ms.date: 08/25/2026
uid: signalr/client-features
---
# ASP.NET Core SignalR clients
Expand Down Expand Up @@ -40,6 +40,7 @@ The table below shows the features and support for the clients that offer real-t
| JSON Hub Protocol |2.1.0|1.0.0|1.0.0|1.0.0|1.0.0-preview.1|
| MessagePack Hub Protocol |2.1.0|1.0.0|1.0.0|5.0.0|1.0.0-preview.1|
| Client Results |7.0.0|7.0.0|7.0.0|7.0.0|1.0.0-preview.1|
| [Authentication Refresh](xref:signalr/authn-and-authz#authentication-refresh) |11.0.0|11.0.0|11.0.0|❌|❌|

Support for enabling additional client features is tracked in [our issue tracker](https://github.com/dotnet/AspNetCore/issues).

Expand Down
18 changes: 17 additions & 1 deletion aspnetcore/signalr/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ author: wadepickett
description: Learn how to configure ASP.NET Core SignalR apps, including allowed transports, logging levels, timeout intervals, and serialization.
monikerRange: '>= aspnetcore-2.1'
ms.author: wpickett
ms.date: 05/20/2026
ms.date: 08/25/2026
uid: signalr/configuration

# customer intent: As an ASP.NET developer, I want to configure ASP.NET Core SignalR, so I can specify my preferences for client and server options.
Expand Down Expand Up @@ -117,6 +117,22 @@ The following table describes options for configuring ASP.NET Core SignalR's adv
| `MinimumProtocolVersion` | 0 | The minimum version of the negotiation protocol. This value is used to limit clients to newer versions of the protocol. |
| `CloseOnAuthenticationExpiration` | `false` | Controls authentication expiration tracking, which closes connections when a token expires. |

:::moniker-end

:::moniker range=">= aspnetcore-11.0"

The following advanced HTTP options configure SignalR *authentication refresh*, introduced in .NET 11. Authentication refresh lets a connected client update its authentication credentials without reconnecting. For more information, see <xref:signalr/authn-and-authz#authentication-refresh>.

| Option | Default value | Description |
| ------ | ------------- | ----------- |
| `EnableAuthenticationRefresh` | `false` | Enables authentication refresh for connections. Enable it together with `CloseOnAuthenticationExpiration` so that connections that aren't refreshed before their token expires are closed. |
| `MaximumAuthenticationExpiration` | `null` | Caps how far in the future a refreshed token's expiration can be set, relative to the time of the refresh. Setting it also gives tokens that have no expiration a known lifetime, which enables automatic refresh. The value must be greater than zero and doesn't apply to Windows authentication. |
| `OnAuthenticationRefresh` | `null` | An optional `Func<AuthenticationRefreshContext, Task<bool>>` callback invoked on each refresh attempt. Return `false` to reject the refresh, which causes the `/refresh` request to fail with an HTTP 403 response. This check runs in addition to the built-in requirement that the refreshed principal map to the same SignalR user. |

:::moniker-end

:::moniker range=">= aspnetcore-8.0"

#### Configure Long Polling transport

The Long Polling transport has other options that can be configured by using the `LongPolling` property:
Expand Down
Loading