Authenticated push and predictable groups. Don't use SignalR for CRUD that HTTP solves. Host: aspnet-core. Language: csharp.
Stack and scope
| Ask | Role |
|---|---|
| Hubs | RPC server → client / client → server |
| IHubContext | Emit from outside the hub (HTTP handlers, jobs) |
| Backplane | Redis (or Azure SignalR) if multi-instance |
| Client | @microsoft/signalr or HubConnection .NET |
WebSockets in Go/Rust: golang-websocket / rust-axum-websocket. gRPC streams: grpc.
Organization
Hubs/
OrdersHub.cs
HubFilters/
Realtime/
OrderNotifier.cs # usa IHubContext
- Thin hub: authz + dispatch. Business rule in the application service.
- Versioned payload contracts (
OrderUpdatedV1).
Non-negotiable rules
- Authenticate the connection (JWT in query/header depending on the client). No public hub "because it's easier".
- Authz per message and per group: join
order:{id}only if the user can see the order. - Multi-replica: backplane or Azure SignalR.
IMemoryonly in a single process. - Timeouts and
KeepAlive. Client reconnects with backoff; the server does not assume an eternal socket. - Don't block the hub with long I/O —
async+ CT; heavy CPU outside. - Small payloads; IDs + the client reloads if it needs the graph.
Hub
[Authorize]
public sealed class OrdersHub : Hub
{
public async Task JoinOrder(Guid orderId)
{
if (!await _authz.CanViewAsync(Context.User, orderId))
throw new HubException("forbidden");
await Groups.AddToGroupAsync(Context.ConnectionId, $"order:{orderId}");
}
}
Context.Userafter auth middleware. MapNameIdentifier→ user id.HubExceptionbecomes an error on the client; don't leak internals.OnConnectedAsync/OnDisconnectedAsyncfor presence; group cleanup.
Outside the hub
await hubContext.Clients.Group($"order:{id}").SendAsync("orderUpdated", dto, ct);
- Call after commit (outbox if the event is critical —
microservices-architecture). - Stable client method names; breaking = version in the name (
orderUpdatedV2).
Client
- Token factory in the
AccessTokenProvider(refresh). withAutomaticReconnect. Re-join groups ononreconnected.- Shared TS/C# typing when there is a contracts package.
Scale
- Azure SignalR Service if ops is already Azure and connection load is the problem.
- Redis backplane: same password/DB; sticky sessions do not replace the backplane for fan-out.
- Limit connections per user if the risk exists.
Anti-patterns
- Hub as a CRUD API
- Broadcast
Clients.Allwith data from one tenant - SignalR on all instances without a backplane
- Auth only on HTTP and anonymous hub
- Payload with a tracked EF entity
Integration with other skills
- HTTP host:
aspnet-core - Tests:
csharp-testing - Authz:
authentication-authorization - Contracts:
api-design
Completion criteria
- Authenticated hub; groups with authz
- Backplane if there is >1 instance
- Client reconnects and rejoins groups
- Events after persistence (or outbox)
- Versioned and small payloads