🚀 SignalR Production: Real-Time Magic at Scale
The Story: Imagine you’re running a super popular pizza delivery service. Customers want to know exactly where their pizza is—right now, not after refreshing the page 100 times. That’s what SignalR does for your apps: instant updates, no refreshing needed!
🎯 What We’re Learning
We’re diving into three superpowers that make SignalR work in the real world:
- Transports – The delivery trucks for your messages
- Authentication – The ID check at the door
- Scaling – Handling millions of hungry customers
🚗 Part 1: SignalR Transports
What Are Transports?
Think of transports as different ways to deliver a letter:
| Transport | Like… | Speed | When Used |
|---|---|---|---|
| WebSockets | Phone call | ⚡ Fastest | Best choice! |
| Server-Sent Events | Radio broadcast | 🏃 Fast | Backup option |
| Long Polling | Asking “Are we there yet?” | 🐢 Slowest | Last resort |
The Pizza Delivery Analogy
- WebSockets = A direct phone line to the pizza shop. You hear instantly when your pizza moves.
- Server-Sent Events = Like listening to a radio station that announces pizza locations.
- Long Polling = Calling the shop every 5 seconds: “Is it here yet? Is it here yet?”
How SignalR Chooses
SignalR is smart! It picks the best transport automatically:
graph TD A["Start Connection"] --> B{WebSockets OK?} B -->|Yes| C["Use WebSockets ⚡"] B -->|No| D{SSE OK?} D -->|Yes| E["Use Server-Sent Events 🏃"] D -->|No| F["Use Long Polling 🐢"]
Code Example: Setting Up Transports
// Server-side: Configure transports
builder.Services.AddSignalR()
.AddHubOptions<ChatHub>(options =>
{
options.EnableDetailedErrors = true;
});
// Client-side: Connect with options
const connection = new signalR
.HubConnectionBuilder()
.withUrl("/chatHub", {
transport: signalR.HttpTransportType
.WebSockets
})
.build();
Real-World Tip
💡 Always prefer WebSockets! They’re 10x faster and use less server power. Only fall back to others when WebSockets aren’t available (like old browsers).
🔐 Part 2: SignalR Authentication
Why Authentication Matters
Imagine if anyone could track any pizza delivery—chaos! Authentication makes sure:
- Only logged-in users can connect
- Users only see their own data
- Bad actors are blocked
The ID Check at the Door
graph TD A["User Wants to Connect"] --> B{Has Valid Token?} B -->|Yes ✅| C[Welcome! Here's your data] B -->|No ❌| D["Sorry, access denied!"]
Two Ways to Authenticate
Method 1: Cookie Authentication (Easy!)
Like showing your membership card at a club:
// Server: Enable cookie auth
builder.Services.AddAuthentication()
.AddCookie();
// Hub: Require logged-in users
[Authorize]
public class ChatHub : Hub
{
public async Task SendMessage(string msg)
{
var user = Context.User.Identity.Name;
await Clients.All
.SendAsync("ReceiveMessage", user, msg);
}
}
Method 2: JWT Token (More Secure!)
Like having a special wristband with your name encoded:
// Server: Configure JWT for SignalR
builder.Services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// Get token from query string
var token = context.Request
.Query["access_token"];
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return Task.CompletedTask;
}
};
});
// Client: Send token when connecting
const connection = new signalR
.HubConnectionBuilder()
.withUrl("/chatHub", {
accessTokenFactory: () => {
return localStorage
.getItem("jwt_token");
}
})
.build();
Getting User Info in Your Hub
public class OrderHub : Hub
{
public async Task GetMyOrders()
{
// Get the logged-in user's ID
var userId = Context.UserIdentifier;
// Send only THEIR orders
await Clients.Caller
.SendAsync("Orders", GetOrdersFor(userId));
}
}
Quick Security Checklist
| ✅ Do This | ❌ Avoid This |
|---|---|
| Use HTTPS always | HTTP in production |
| Validate tokens | Trust client data |
| Set token expiry | Tokens that never expire |
| Log failed attempts | Ignoring auth errors |
📈 Part 3: SignalR Scaling
The Problem: One Server Isn’t Enough
Your pizza tracker is too popular! One server can handle maybe 10,000 connections. But you have 100,000 hungry customers!
The Solution: Multiple Servers + Backplane
Think of a backplane as a walkie-talkie system:
graph TD A["User A on Server 1"] --> B["Redis Backplane"] C["User B on Server 2"] --> B D["User C on Server 3"] --> B B --> A B --> C B --> D style B fill:#ff6b6b
When Server 1 sends a message, Redis tells Server 2 and Server 3: “Hey, pass this along!”
Setting Up Redis Backplane
// Install: Microsoft.AspNetCore
// .SignalR.StackExchangeRedis
builder.Services.AddSignalR()
.AddStackExchangeRedis(
"localhost:6379",
options =>
{
options.Configuration
.ChannelPrefix = "MyApp";
});
That’s it! Now your servers talk to each other through Redis.
Azure SignalR Service (The Easy Button)
Don’t want to manage servers? Let Azure do the heavy lifting:
// Install: Microsoft.Azure.SignalR
builder.Services.AddSignalR()
.AddAzureSignalR("your-connection-string");
Scaling Options Compared
| Option | Connections | Difficulty | Cost |
|---|---|---|---|
| Single Server | 10K | Easy | $ |
| Redis Backplane | 100K+ | Medium | $ |
| Azure SignalR | 1M+ | Easy | $$ |
Sticky Sessions: The Gotcha!
⚠️ Important! When using load balancers, enable sticky sessions so users stay connected to the same server.
# Nginx example
upstream signalr {
ip_hash; # Sticky sessions!
server server1:5000;
server server2:5000;
}
Scaling Best Practices
- Start simple – One server is fine for small apps
- Add Redis – When you hit 5K+ connections
- Go Azure – When you need 100K+ and don’t want server headaches
- Monitor everything – Track connection counts, message rates, errors
🎉 Putting It All Together
Here’s a production-ready SignalR setup:
var builder = WebApplication.CreateBuilder(args);
// 1. Authentication
builder.Services.AddAuthentication()
.AddJwtBearer();
// 2. SignalR with Redis scaling
builder.Services.AddSignalR()
.AddStackExchangeRedis("redis:6379");
var app = builder.Build();
// 3. Secure WebSocket endpoint
app.UseAuthentication();
app.UseAuthorization();
app.MapHub<ChatHub>("/chatHub")
.RequireAuthorization();
app.Run();
🧠 Key Takeaways
| Topic | Remember This! |
|---|---|
| Transports | WebSockets = best, Long Polling = backup |
| Authentication | JWT tokens + query string for SignalR |
| Scaling | Redis backplane or Azure SignalR Service |
🚀 You’re Ready!
You now understand how SignalR works in production:
- ⚡ Transports deliver messages at the speed of light
- 🔐 Authentication keeps the bad guys out
- 📈 Scaling handles millions of users
Go build something amazing! Your real-time app is waiting. 🎊
