Defending against XSS input injection in ASP.NET.
ASP.NET gives you strong XSS defenses — but only if you let it. A short field guide to output encoding, the Html.Raw trap, Content Security Policy and cookie hardening, with the .NET code that makes each one real.
The good news for .NET developers is that the platform encodes by default. The risk is almost always something that switched that default off — a raw render, a hand-built HTML string, an unsafe redirect. Defense is a small set of habits, each backed by a framework feature.
1. Let Razor encode — and never bypass it casually
In Razor, @model.Name is HTML-encoded automatically; the dangerous call is @Html.Raw(model.Name), which emits the value verbatim. Treat Html.Raw as a security decision, not a convenience, and never hand it user-controlled data.
// SAFE: Razor encodes <, >, &, " for you
<span>@Model.DisplayName</span>
// DANGEROUS: raw output of untrusted data is stored XSS
<span>@Html.Raw(Model.Bio)</span>2. Encode for the right context
HTML body, an HTML attribute, a URL and a block of JavaScript are four different grammars, and each needs its own encoder. .NET ships them in System.Text.Encodings.Web — HtmlEncoder, UrlEncoder and JavaScriptEncoder. Reaching for the matching encoder is what closes attribute-break-out and script-context attacks that a single HTML encode would miss.
// inject an HtmlEncoder and use it where Razor cannot
var safe = HtmlEncoder.Default.Encode(userValue);
var href = UrlEncoder.Default.Encode(redirectTarget);3. Add a Content Security Policy
Encoding stops injection; CSP contains the blast radius if something slips through. A policy that forbids inline script and restricts sources means an injected <script> simply will not run.
// minimal CSP middleware in Program.cs
app.Use(async (ctx, next) => {
ctx.Response.Headers["Content-Security-Policy"] =
"default-src 'self'; script-src 'self'; object-src 'none'";
await next();
});4. Harden the cookies XSS wants most
The usual goal of XSS is the session cookie. Marking authentication cookies HttpOnly puts them out of JavaScript’s reach, and Secure keeps them on TLS — so even a successful injection cannot read the token it was after.
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax;Where B5 fits
These habits are defense in depth, and B5 Secure assumes them. The framework’s XSS protection validates and encodes untrusted input as part of the request pipeline rather than leaving it to each controller to remember — the same “Never Trust” default that signs every request and fails closed when a check cannot complete.
Make the secure path the default path.
In B5 Secure, encoding and validation are part of the pipeline, not an afterthought a controller has to opt into.
Explore Never Trust →