Blazor security checklist: 12 things to check before go-live
Twelve Blazor security checks for business apps on .NET 10: authorization in services, secrets, WebAssembly, forms, error details, CSP and vulnerable packages.
Vlado Pandžić · Founder · Senior .NET architect
Published · 6 min read
Most security incidents in business applications are not clever. A key committed to the repository, a token that never expires, a screen that hides a button but not the action behind it. Blazor comes with sensible defaults, but it also makes UI code and server code look almost the same, and that is exactly where the easy mistakes hide.
Here are twelve checks worth going through before a Blazor application goes live, or before the next audit. Some apply everywhere, some only to a particular render mode.
Everywhere
1. Authorization belongs in the service, not only in the UI
AuthorizeView decides what the user sees. It does not decide what the user can do.
<AuthorizeView Roles="Finance">
<button @onclick="() => Payments.ApproveAsync(payment.Id, user)">Approve payment</button>
</AuthorizeView>
Hiding the button is good UX, but the protection has to be in the method that does the work, so that it holds no matter which screen, API or future developer calls it:
public sealed class PaymentService(IAuthorizationService authorization, PaymentRepository payments)
{
public async Task ApproveAsync(int paymentId, ClaimsPrincipal user)
{
var result = await authorization.AuthorizeAsync(user, "ApprovePayments");
if (!result.Succeeded)
throw new UnauthorizedAccessException();
await payments.ApproveAsync(paymentId);
}
}
Pages are protected with @attribute [Authorize], and sensitive operations with a policy in the service. Both, not one or the other. The same rule applies to an AI assistant inside the application.
2. No secrets in the repository
Connection strings, API keys and certificates belong in Azure Key Vault or in the hosting environment’s settings, and locally in Secret Manager, not in appsettings.json. If a key has ever been committed, deleting it is not enough, because it stays in the git history. It has to be rotated.
3. Tokens and keys that expire
API keys with no expiry date and sessions that last for months are an open door that nobody remembers. Every key needs an owner, an expiry date and a way to be revoked.
4. Known vulnerabilities in NuGet packages
Since the .NET 8 SDK, NuGet warns about packages with known vulnerabilities during restore. Those warnings must not be suppressed and forgotten. For older applications, our free .NET Framework migration tool shows which of your packages have known vulnerabilities.
5. Raw HTML only from trusted sources
Blazor encodes everything it renders, so text a user types cannot become a script. The exception is MarkupString, which renders HTML as it is. If that HTML comes from users, emails or external systems, it has to be sanitised first.
6. Detailed errors only in development
DetailedErrors sends the full error with stack trace details to the browser. That is useful on a developer’s machine and a gift to an attacker in production. In production, the user gets a short message, and the details go to the log, for example to Application Insights.
7. Limits on sign-in and expensive operations
Sign-in, password reset and exports that hit the database hard need a limit on how many requests a user or an address can send. ASP.NET Core has this built in with AddRateLimiter.
8. Security headers and CSP
A Content Security Policy tells the browser which scripts it may run. Blazor has its own requirements: a WebAssembly application needs wasm-unsafe-eval in the policy. Set the policy deliberately and test it, rather than leaving it out because “something stopped working”.
Static forms
9. Antiforgery protection
In a Blazor Web App, forms rendered on the server are protected against cross-site request forgery when the application calls app.UseAntiforgery(). EditForm adds the token by itself, while a plain HTML <form> needs the <AntiforgeryToken /> component.
Interactive Server
10. Do not raise the connection limits blindly
With Interactive Server, every user holds a connection and memory on the server. Blazor has limits on message size and on how much it buffers per user, and raising them “because an upload failed” opens the way to exhausting the server. Large files belong in a dedicated upload, not in one huge message. How memory per user adds up is covered in the article on slow Blazor applications.
WebAssembly
11. Nothing secret in the client
Everything a WebAssembly application contains is downloaded to the browser: the code, the configuration and the libraries. Microsoft is explicit that connection strings, keys, passwords and private code must never be placed there. Authorization checks in the browser can be bypassed, so every API has to check permissions again on the server.
12. Tokens stay on the server
Access tokens in the browser’s local storage are an easy target. Microsoft’s recommended approach for Blazor Web Apps is the Backend for Frontend pattern: the server holds the tokens and talks to the APIs, and the browser only gets a secure cookie.
The checklist in one table
| # | Check | Applies to |
|---|---|---|
| 1 | Authorization in services, not only in the UI | Everywhere |
| 2 | No secrets in the repository | Everywhere |
| 3 | Keys and tokens that expire | Everywhere |
| 4 | No known vulnerabilities in NuGet packages | Everywhere |
| 5 | MarkupString only for trusted HTML |
Everywhere |
| 6 | Detailed errors off in production | Everywhere |
| 7 | Limits on sign-in and expensive operations | Everywhere |
| 8 | Deliberate CSP and security headers | Everywhere |
| 9 | Antiforgery for forms | Static forms |
| 10 | Connection limits left in place | Interactive Server |
| 11 | Nothing secret in the client | WebAssembly |
| 12 | Tokens on the server (BFF) | WebAssembly |
Which render mode each screen should use, and what that means for security, is covered in Blazor Server vs WebAssembly vs Auto.
How we work
We review Blazor and .NET applications against exactly this list, and fix what we find, from authorization that only lived in the UI to keys that ended up in the repository. You get a prioritised list of risks, from the ones worth fixing this week to the ones that can wait. More about our Blazor work, and the first step is a free 30-minute call.
Sources
- ASP.NET Core Blazor authentication and authorization, Microsoft Learn
- Secure ASP.NET Core Blazor WebAssembly, Microsoft Learn
- Threat mitigation for interactive server-side rendering, Microsoft Learn
- Secure a Blazor Web App with OpenID Connect, Microsoft Learn
- Content Security Policy for Blazor, Microsoft Learn
- Blazor forms and antiforgery, Microsoft Learn
- Handle errors in Blazor apps, Microsoft Learn
- Rate limiting middleware, Microsoft Learn
- Auditing NuGet packages for security vulnerabilities, Microsoft Learn
This article is general information only, not legal, tax, financial or other professional advice. Scenarios, examples and calculations are illustrative. Terms of use and disclaimer.