Securing Blazor Applications: Top 10 Strategies for 2026
Blazor changed the .NET web security model in ways most teams still underestimate. Blazor Server holds a long-lived stateful circuit over SignalR for every connected user, turning every component event into a server-side method call. Blazor WebAssembly ships your component logic to the browser, which means anything in your assemblies is reverse-engineerable. Blazor Auto (the .NET 8 LTS+ default that mixes both) inherits both threat models at once.
This is the security playbook we apply to every Blazor deployment on Adaptive's Blazor Server hosting and Blazor WebAssembly hosting, updated for .NET 10 LTS and the post-quantum cryptography wave hitting production in 2026. Ten strategies, each with code, the specific failure mode it prevents, and how Adaptive's infrastructure helps you implement it.
.NET 10LTS — updated for
PQQuantum-safe TLS
FREESSL + WAF on every plan
Why Blazor security is different
Three things make Blazor's threat surface unusual compared to a plain ASP.NET Core MVC or Minimal API app:
Server-side state per user. Blazor Server keeps a circuit alive in server memory for every connected user. An attacker who finds a way to spam circuit creation can exhaust your RAM in seconds — DoS via state, not just via requests.
Client-side assembly inspection. Blazor WASM ships compiled .dll files to the browser. Anyone with browser dev tools can read your component logic, including any hardcoded API endpoints, embedded secrets, or business rules.
Mixed-mode authentication. Blazor Auto switches between server-rendered SSR, server-side interactive (SignalR), and client-side interactive (WASM) on the same page. Each mode has different anti-forgery, cookie, and CSRF requirements.
If you're still picking between modes, read our Blazor Server vs WebAssembly hosting decision guide first — the security posture follows the hosting model.
- Lock Down SignalR for Blazor Server
Every Blazor Server app communicates over a SignalR hub. By default, the hub is anonymous-friendly and serves any origin that can reach your site. In production, you need three guardrails:
builder.Services.AddSignalR(options =>
{
options.MaximumReceiveMessageSize = 32 * 1024; // 32 KB cap per message
options.EnableDetailedErrors = false; // never leak stack traces
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
});
builder.Services.Configure<HubOptions>(o =>
{
o.MaximumParallelInvocationsPerClient = 4;
});
// Restrict the transport — WebSockets only, no long-polling fallback
app.MapBlazorHub(options =>
{
options.Transports = HttpTransportType.WebSockets;
});
What this prevents: oversized payload DoS (32 KB cap), parallel-invocation abuse, and the long-polling fallback attack where a misbehaving client opens many polling connections to chew up resources.
🛡️ Adaptive infrastructure cooperates: The WAF in front of every Adaptive plan rate-limits per-IP connection attempts before they even reach SignalR. Combined with dedicated IIS Application Pools (1–4 GB RAM per plan), a SignalR flood on one tenant cannot pivot to others.
- Validate Every [Parameter] on the Server
Blazor's [Parameter] attributes feel like trusted compile-time bindings. They are not. For Blazor Server, every parameter comes through the SignalR circuit — the client can forge any value. For Blazor WASM, the values come from query strings, route segments, or JS interop.
[Parameter]
public int OrderId { get; set; }
protected override async Task OnParametersSetAsync()
{
// Authorization check on every render. The Order belongs to the
// current user, OR they have a role that can view all orders.
var user = await _authState.GetAuthenticationStateAsync();
var order = await _orders.GetOrderAsync(OrderId);
if (order is null) throw new InvalidOperationException("Order not found");
if (order.CustomerId != user.User.GetUserId()
&& !user.User.IsInRole("OrderAdmin"))
{
throw new UnauthorizedAccessException();
}
_order = order;
}
What this prevents: the IDOR (Insecure Direct Object Reference) class of bugs — where an attacker changes the URL from /order/123 to /order/124 and sees a different customer's order. This is the single most-exploited Blazor vulnerability we see in real production audits.
- Use AuthorizeView and AuthorizeRouteView Correctly
<AuthorizeView> hides UI from unauthenticated users — but it does not stop the underlying code from running. A common mistake:
<!-- WRONG: AdminPanel still runs OnInitializedAsync even for non-admins -->
<AuthorizeView Roles="Admin">
<Authorized>
<AdminPanel />
</Authorized>
</AuthorizeView>
The component is constructed and its lifecycle runs regardless of the authorize check — the check only gates the rendered output. If AdminPanel.OnInitializedAsync() fetches sensitive data, that data is loaded even when the UI is hidden.
<!-- RIGHT: route-level guard plus per-component [Authorize] -->
@page "/admin/panel"
@attribute [Authorize(Roles = "Admin")]
<AdminPanel />
For routing, use <AuthorizeRouteView> in your App.razor so unauthenticated requests redirect before any page-level lifecycle runs:
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)" />
</Found>
</Router>
</CascadingAuthenticationState>
What this prevents: data leakage when OnInitializedAsync queries a database before the authorize check renders an empty fragment. Common pattern in admin dashboards where the data is "hidden" but still loaded.
- Short-Lived Access Tokens with PKCE + Refresh Rotation
Blazor WASM cannot keep a secret. Anything in your component code, configuration, or assembly metadata is browser-readable. So protecting your APIs requires three things:
Authorization Code flow with PKCE — never the implicit flow, even though it's "still supported." The implicit flow exposes access tokens in URL fragments where they leak to referrer logs.
Access tokens with 5–15 minute lifetimes — short enough that stolen tokens expire before they're useful at scale.
Refresh token rotation — every refresh issues a new refresh token AND invalidates the previous one. If a stolen refresh leaks, the legitimate client's next refresh fails, surfacing the breach in your logs.
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("OIDC", options.ProviderOptions);
options.ProviderOptions.ResponseType = "code"; // not "token"
options.ProviderOptions.DefaultScopes.Add("offline_access"); // refresh tokens
});
What this prevents: "harvest now, replay later" attacks against tokens captured via XSS, browser extension malware, or compromised dev tools. Combine with the next two strategies (storage hygiene + CSP) for defense in depth.
- Storage Hygiene — Never Put Tokens in localStorage
Every Blazor WASM tutorial we've audited recommends some flavor of localStorage for tokens. Stop doing this. localStorage is readable by any JavaScript executing in the same origin — including any XSS payload that gets past your defenses.
Use HTTP-only, Secure, SameSite=Strict cookies for session and refresh tokens. The backend-for-frontend (BFF) pattern is the cleanest approach:
Blazor WASM never sees the access token.
The ASP.NET Core host issues an HTTP-only session cookie when the user signs in.
Every API call from WASM goes through the host, which attaches the access token server-side before forwarding.
// BFF endpoint — adds the OAuth access token before forwarding
app.MapGet("/bff/api/{**path}", async (HttpContext ctx, IHttpClientFactory factory) =>
{
var token = await ctx.GetTokenAsync("access_token");
var client = factory.CreateClient("DownstreamApi");
client.DefaultRequestHeaders.Authorization = new("Bearer", token);
// forward request, stream response back
}).RequireAuthorization();
What this prevents: 99% of practical token theft. XSS payloads can call your APIs (because the session cookie is sent automatically), but the access token itself never lives in JavaScript reach. Defenders win the asymmetric battle by keeping the secret server-side.
🔒 Adaptive's setup makes BFF natural: Every Adaptive ASP.NET Core hosting plan ships with the IIS reverse proxy in front of Kestrel. The reverse proxy is the perfect BFF host — your Blazor WASM and your token forwarding live on the same origin, same cookie domain, zero cross-origin complexity.
- Disable Detailed Errors in Production Circuits
Blazor Server's default DetailedErrors = false is correct for production, but the circuit-level setting is separate from the SignalR-level one. Both must be off:
builder.Services.AddServerSideBlazor(options =>
{
options.DetailedErrors = false;
options.DisconnectedCircuitMaxRetained = 100;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(3);
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(1);
options.MaxBufferedUnacknowledgedRenderBatches = 10;
});
When DetailedErrors is on, every unhandled exception sends the full stack trace through the SignalR circuit to the browser, where it's visible in the browser console. This routinely leaks database connection strings, file system paths, and internal class names that map directly to your domain model.
What this prevents: stack-trace exfiltration during reconnaissance. An attacker triggers exceptions deliberately to map your dependency graph; if errors are detailed, the recon takes minutes instead of weeks.
- Anti-Forgery Across SSR, Server-Interactive, and WASM Boundaries
Blazor Auto (the .NET 8 LTS+ default) renders the same page in three modes: SSR for first paint, server-interactive for the hydrated session, then optionally WASM after the runtime downloads. Anti-forgery has to work across all three.
// Program.cs — Blazor Auto with proper anti-forgery
builder.Services.AddAntiforgery(o =>
{
o.Cookie.SecurePolicy = CookieSecurePolicy.Always;
o.Cookie.HttpOnly = true;
o.Cookie.SameSite = SameSiteMode.Strict;
o.HeaderName = "X-XSRF-TOKEN";
});
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
app.UseAntiforgery(); // BEFORE app.MapRazorComponents
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode();
For SSR form submissions, decorate the form with the explicit anti-forgery field:
<EditForm Model="@_model" OnValidSubmit="HandleSubmit" FormName="contact">
<AntiforgeryToken />
<!-- form fields -->
</EditForm>
For WASM HTTP calls, register the anti-forgery handler so the X-XSRF-TOKEN header is attached automatically.
What this prevents: CSRF on the SSR-rendered forms (most-overlooked attack vector in Blazor Auto) and request-forgery from compromised cross-origin contexts.
- Content Security Policy with Nonces for Blazor's Runtime
Blazor injects <script> tags for _framework/blazor.web.js and the WebAssembly runtime. A strict CSP without unsafe-inline requires per-request nonces.
// Middleware — generate a nonce per request, expose via HttpContext.Items
app.Use(async (context, next) =>
{
var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(16));
context.Items["csp-nonce"] = nonce;
context.Response.Headers["Content-Security-Policy"] =
$"default-src 'self'; " +
$"script-src 'self' 'nonce-{nonce}' 'wasm-unsafe-eval'; " +
$"style-src 'self' 'nonce-{nonce}'; " +
$"img-src 'self' data: https:; " +
$"connect-src 'self' wss:; " +
$"frame-ancestors 'none'; " +
$"base-uri 'self'; " +
$"form-action 'self'";
await next();
});
Then in your App.razor:
@inject IHttpContextAccessor Http
<script src="_framework/blazor.web.js" nonce="@Nonce" suppress-error="BL9992"></script>
@code {
string Nonce => Http.HttpContext?.Items["csp-nonce"]?.ToString() ?? "";
}
The wasm-unsafe-eval directive is needed for the Mono WebAssembly runtime. It's narrower than unsafe-eval — it only permits WebAssembly compilation, not arbitrary JavaScript eval().
What this prevents: reflected XSS escalation. Even if an attacker injects a <script> tag through a vulnerable component, the browser refuses to execute it because the nonce doesn't match.
- Rate-Limit Circuit Creation and Dispose Abandoned Circuits
The DoS vector unique to Blazor Server: an attacker opens thousands of WebSocket connections, each creating a new circuit, each occupying ~250 KB of memory. Within minutes, your worker process is out of memory. (For the per-circuit math, see our Blazor Server RAM sizing guide.)
Defenses, layered:
// 1. Rate-limit connection establishment with the .NET 7+ rate limiter
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("BlazorHub", context =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "anon",
factory: _ => new FixedWindowRateLimiterOptions
{
Window = TimeSpan.FromMinutes(1),
PermitLimit = 10,
QueueLimit = 0
}));
});
app.MapBlazorHub().RequireRateLimiting("BlazorHub");
// 2. Aggressive disconnect cleanup
builder.Services.AddServerSideBlazor(o =>
{
o.DisconnectedCircuitMaxRetained = 50; // keep fewer
o.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(1); // for less time
});
What this prevents: circuit-creation flood that maps directly to memory exhaustion. The rate limiter caps new circuits at 10 per IP per minute; the aggressive disconnect cleanup frees memory faster when legitimate users close their tabs.
📊 Adaptive plans + Blazor circuit budgets: the three Blazor hosting tiers map to predictable circuit ceilings — ASP.NET Developer ($9.49) for ~75-100 concurrent circuits, ASP.NET Business ($17.49) for ~150-200, ASP.NET Professional ($27.49) for ~300-400+. Sized correctly, the rate limiter prevents tail-abuse without affecting legitimate users.
- Audit Your Component Library Dependencies on a Schedule
The Blazor ecosystem leans heavily on third-party component libraries — MudBlazor, Radzen Blazor, SyncFusion, Telerik, Blazorise. Each introduces JavaScript, CSS, and sometimes server-side helpers. Each carries CVE risk.
Run dotnet list package --vulnerable --include-transitive weekly. .NET 10 LTS ships with dotnet audit baked in.
Subscribe to GitHub security advisories for every library you depend on.
Set up Dependabot or Renovate to auto-PR security updates within 24 hours of release.
For commercial libraries (SyncFusion, Telerik), monitor their security bulletins directly — these don't always show up in GitHub.
Patch SLA we commit to in production: 14 days for Critical CVEs, 30 days for High. Faster if the CVE is exploitable in your specific attack surface.
For the broader .NET dependency-patching playbook including NuGetAudit, see our ASP.NET Core security best practices article.
Hosting-Layer Security Adaptive Provides
Even with perfect Blazor code, your hosting layer has to cooperate. Every Adaptive Web Hosting plan includes:
LayerWhat's Included
EdgeDDoS protection, managed Web Application Firewall — useful against SignalR floods before they reach your circuits
NetworkTLS 1.3 termination, FREE SSL on every site, post-quantum-ready on .NET 10 LTS hosting
HostHardened Windows Server 2022 baseline, regular patching, Server Core editions for reduced attack surface
IISDedicated Application Pools per site (1–4 GB RAM by plan tier) — circuit exhaustion in one app can't pivot to others
StorageHigh-performance SSD, automated backups, encryption at rest
DatabaseReal SQL Server 2022 with Always Encrypted, ledger tables, Query Store — not a stripped-down MySQL substitute
InfrastructureAWS US-East data center, 99.99% uptime SLA, 30-day money-back guarantee on every plan
Frequently Asked Questions
Is Blazor Server safe to use over public internet?
Yes — with the guardrails above. Blazor Server's stateful circuit model adds DoS surface (strategy #9) and connection-management complexity (strategy #1), but the security model is otherwise mature and battle-tested in thousands of production deployments. Microsoft Learn, the Bing Maps admin portal, and many .NET Foundation projects run on it.
Can someone reverse-engineer my Blazor WASM application?
Yes — anyone with browser dev tools can download your .dll files and inspect them with ILSpy or dnSpy. This is intrinsic to running .NET in the browser. The security implication: never put secrets, business rules, or sensitive logic in WASM-bound code. Keep those server-side, expose them through authenticated APIs.
Does Blazor Server require sticky sessions on load-balanced setups?
Yes. The circuit lives in the specific server's memory; reconnecting to a different node loses state. Use sticky sessions (cookie-based) at the load balancer, or use Azure SignalR Service / Redis backplane for transparent state distribution. On Adaptive's Blazor Server hosting, single-node deployments avoid this complexity entirely — vertical scale within the plan's RAM budget covers most production workloads.
How does SignalR security differ between Blazor Server and standalone SignalR hubs?
Blazor Server uses the same SignalR transport but wraps it in the circuit abstraction, which adds opinionated lifecycle management. The defensive guardrails (message size limits, parallel invocation caps, transport restriction) are the same. The big difference: Blazor circuits are always authenticated against your AuthenticationStateProvider, so unauthenticated probing is naturally rate-limited by your sign-in flow. For why your SignalR connection might drop unexpectedly, see our SignalR connection dropping guide.
Should I use ASP.NET Core Identity or an external provider for Blazor auth?
External providers (Microsoft Entra ID, Auth0, Okta, Keycloak) are the safer default for new deployments. They handle MFA, passkeys, breach detection, and account-takeover prevention for you. ASP.NET Core Identity is appropriate when you have specific data residency or licensing constraints — both are production-grade if configured correctly.
What's the right Blazor mode for a high-security application?
Blazor Server. Sensitive logic stays on the server; the browser sees only rendered HTML and DOM diffs. WASM exposes your code, which is fine for public-facing apps but bad for anything where the business logic itself is a secret. For the full mode comparison, see our Blazor Server vs WebAssembly hosting model guide.
Does post-quantum cryptography matter for a typical Blazor app in 2026?
Not urgently for most apps — but "harvest now, decrypt later" attacks make PQ matter for any data with a 10+ year secrecy requirement. .NET 10 LTS supports ML-KEM (key exchange) and ML-DSA (signatures) natively, and Adaptive's TLS layer is PQ-ready, so you can enable hybrid certs with a one-line change when your threat model justifies it.
How often should we run security audits on a Blazor app?
Quarterly external pentests are the minimum for production Blazor apps handling payment, health, or identity data. Internal audits monthly. Continuous: SAST in CI (CodeQL, Snyk Code), dependency scanning on every PR, and OWASP ZAP DAST in staging on every deploy. For the broader checklist, see our ASP.NET Core security best practices.
Bottom line
Blazor's security model is solid — but it puts more responsibility on the application developer than most stacks, because the stateful circuit + browser-shipped assemblies + mixed-mode rendering combine threat surfaces that pure-server or pure-SPA frameworks don't share. The ten strategies above are the ones we apply on every production Blazor deployment, and they're what separates a Blazor app from a Blazor incident.
On Adaptive Web Hosting's Blazor plans, the security primitives — WAF, DDoS protection, dedicated IIS Application Pools, hardened Windows Server 2022, FREE SSL, and post-quantum-ready TLS 1.3 on .NET 10 LTS — are included on every tier. ASP.NET Developer ($9.49/mo) for development and smaller production apps, ASP.NET Business ($17.49/mo) for sites that need to stay fast under load, ASP.NET Professional ($27.49/mo) for agencies and SaaS builders managing multiple applications. Every plan ships with a 30-day money-back guarantee. View Blazor hosting plans, read our complete Blazor hosting guide, or talk to a Blazor engineer about a specific security requirement.