Authorization
SignalARRR integrates with ASP.NET Core's authorization system. Apply [Authorize] at the method, class, or hub level.
SignalARRR supports two authentication modes:
- Message-Level (Bearer, Basic, API Key) — token sent per message, automatic challenge/refresh on expiry
- Transport-Level (client certificates, cookies, Windows/Negotiate) — authenticated at connection time, server-side re-validation on cache expiry
Method-level authorization
Apply [Authorize] to individual methods:
public class AdminMethods : ServerMethods<AppHub>, IAdminHub
{
[Authorize(Policy = "AdminOnly")]
public Task DeleteUser(string userId) { ... }
[Authorize(Roles = "Admin,Moderator")]
public Task BanUser(string userId) { ... }
[AllowAnonymous]
public Task<string> GetServerInfo() { ... }
}Class-level authorization
Apply [Authorize] to the entire class — all methods require authentication:
[Authorize]
public class SecureMethods : ServerMethods<AppHub>, ISecureHub
{
public Task GetSecret() { ... } // requires authentication
[AllowAnonymous]
public Task<string> GetPublicData() { ... } // opt-out for this method
}Hub-level inheritance
If the hub class itself has [Authorize], all ServerMethods<T> classes inherit it automatically:
[Authorize]
public class SecureHub : HARRR
{
public SecureHub(IServiceProvider sp) : base(sp) { }
}
// All methods in this class require authentication, inherited from the hub
public class SecureMethods : ServerMethods<SecureHub>, ISecureHub { ... }Message-Level Authentication (Tokens)
Authentication setup
Configure ASP.NET Core authentication as usual. SignalARRR reads the Authorization header from client requests:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = "your-issuer",
ValidAudience = "your-audience",
IssuerSigningKey = new SymmetricSecurityKey(key)
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
});Client-side token provider
.NET Client
Provide a token factory when creating the connection:
var connection = HARRRConnection.Create(builder =>
{
builder.WithUrl("https://localhost:5001/apphub", options =>
{
options.AccessTokenProvider = () => Task.FromResult(GetCurrentToken());
});
});TypeScript Client
const connection = HARRRConnection.create(builder => {
builder.withUrl('https://localhost:5001/apphub', {
accessTokenFactory: () => getAuthToken(),
});
});Automatic token challenge
When a client's token expires during an active connection, SignalARRR doesn't disconnect it. Instead, the next authorized method call triggers a challenge flow:
- Server detects the cached authentication has expired
- Server sends
ChallengeAuthenticationto the client (via SignalR's native client results) - Client's
AccessTokenProvideris called to get a fresh token - Client returns the new token directly from the handler
- Server validates the new token against the configured authentication scheme and continues the request
This happens transparently — no client-side code needed beyond providing a token factory.
When [Authorize] is used without specifying a scheme (the common case), SignalARRR automatically uses the default authentication scheme configured via AddAuthentication().
Transport-Level Authentication (Certificates, Cookies, Negotiate)
For scenarios where credentials exist at the transport layer (TLS client certificates, HTTP cookies, Windows/Negotiate), SignalARRR supports transport-level authentication. The client authenticates once at connection time, and the server re-validates credentials server-side when the auth cache expires — no challenge round-trip needed.
How it works
- Client connects with transport credentials (e.g., client certificate via mTLS)
- ASP.NET Core authenticates during SignalR negotiate —
ClientContext.Useris set - SignalARRR auto-detects transport-level auth (client cert present, or Negotiate/NTLM/Kerberos auth type)
- On cache expiry: server re-validates the stored credentials server-side (no challenge to client)
- If re-validation fails (cert expired, revoked, etc.) → request is rejected
Client certificate authentication
Server setup
Configure Kestrel for client certificates and add an authentication handler:
builder.WebHost.UseKestrel(kestrel =>
{
kestrel.Listen(IPAddress.Any, 5001, listenOptions =>
{
listenOptions.UseHttps(https =>
{
https.ServerCertificate = serverCert;
https.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
});
});
});
builder.Services.AddAuthentication("Certificate")
.AddCertificate(options =>
{
options.AllowedCertificateTypes = CertificateTypes.All;
options.RevocationMode = X509RevocationMode.Online;
});.NET Client
Configure the client certificate on the connection — no AccessTokenProvider needed:
var cert = new X509Certificate2("client.pfx", password);
var connection = HARRRConnection.Create(builder =>
{
builder.WithUrl("https://server:5001/apphub", options =>
{
options.ClientCertificates = new X509CertificateCollection { cert };
options.HttpMessageHandlerFactory = handler =>
{
if (handler is SocketsHttpHandler socketsHandler)
{
socketsHandler.SslOptions.ClientCertificates =
new X509CertificateCollection { cert };
}
return handler;
};
});
});No token provider needed
When using transport-level auth, do not set AccessTokenProvider. SignalARRR automatically detects that the client uses transport-level credentials and re-validates server-side instead of sending a challenge.
Certificate re-validation
When the auth cache expires, the server re-validates the stored client certificate:
- Expiry check:
NotBefore/NotAfterdates - Revocation check: CRL/OCSP (configurable)
- Custom validation: Pluggable callback for custom logic
Configure via SignalARRRServerOptions:
builder.Services.AddSignalARRR(options => options
.AddServerMethodsFrom(typeof(Program).Assembly)
.WithCertificateRevocationCheck(true) // default: true
.WithCertificateRevocationMode(X509RevocationMode.Online) // default: Online
.WithCustomCertificateValidator(cert => // optional
{
// Custom logic, e.g., check against an internal revocation list
return !IsRevoked(cert.Thumbprint);
}));Custom re-validation service
For full control over transport-auth re-validation, implement ITransportAuthRevalidationService:
public class MyRevalidationService : ITransportAuthRevalidationService
{
public async Task<bool> RevalidateAsync(
ClientContext clientContext,
CancellationToken cancellationToken = default)
{
if (clientContext.ClientCertificate != null)
{
// Check your internal revocation database, OCSP responder, etc.
return await CheckCertificateStatus(clientContext.ClientCertificate);
}
// Non-cert transport auth (cookies, Negotiate)
return clientContext.User.Identity?.IsAuthenticated == true;
}
}
// Register before AddSignalARRR (TryAddSingleton won't override your registration)
builder.Services.AddSingleton<ITransportAuthRevalidationService, MyRevalidationService>();Certificate rotation
To rotate certificates without restarting the application:
- Update the certificate file on disk
- Reconnect the SignalR connection — the new TLS handshake uses the new certificate
- Server validates the new certificate on connect
// Client-side cert rotation
await connection.StopAsync();
// Certificate file has been updated on disk — reload it
cert = new X509Certificate2("client.pfx", password);
await connection.StartAsync(); // new TLS handshake with new certMixed mode
A single hub can serve both token-based and certificate-based clients simultaneously. SignalARRR detects the authentication mode per client:
[Authorize]
public class AppHub : HARRR
{
public AppHub(IServiceProvider sp) : base(sp) { }
}- Client A connects with
AccessTokenProvider→ message-level auth, challenge on expiry - Client B connects with client certificate → transport-level auth, server-side re-validation
Auth cache
Authentication results are cached per client (default: 3 minutes). When a client connects to a hub with [Authorize], the cache is initialized from the SignalR negotiate authentication, so the first method call uses the cached principal without triggering a challenge or re-validation.
The cache duration is configurable:
builder.Services.AddSignalARRR(options => options
.AddServerMethodsFrom(typeof(Program).Assembly)
.WithAuthCacheDuration(TimeSpan.FromMinutes(5)));The cache duration controls how often credentials are re-checked — for both token-based and transport-level auth.
ClientContext user data
Inside ServerMethods, access the authenticated user through ClientContext:
public Task<string> GetUserInfo()
{
var user = ClientContext.User;
var name = user.FindFirst(ClaimTypes.Name)?.Value;
var roles = user.FindAll(ClaimTypes.Role).Select(c => c.Value);
return Task.FromResult($"{name} ({string.Join(", ", roles)})");
}Next steps
- Client Manager — query authenticated clients
- Connection Setup (.NET) — configure token providers
- TypeScript Setup — authentication in the TypeScript client