Prevent Cross Site Scripting XSS ASP NET Core: How to Secure Your Web App

We’ve all stared down a blank code editor, thinking our app’s solid, airtight, ready to ship. But then—bam. A script injection sneaks in. Not because we didn’t care, but because we assumed something we shouldn’t. 

That’s how Cross-Site Scripting (XSS) gets us. It hides in the little spots—comments, profile fields, maybe even some JSON payload we forgot to sanitize. In ASP.NET Core, the best defense we’ve got is to slow down, think about every input, and build habits that keep the dirty stuff out.

We’ve caught myself skipping a validation step once, thinking, “Oh, it’s internal data, nobody can touch it.” Turns out, it came from a partner API—and that thing was a mess. Now we treat all data as suspicious until proven safe. We validate early, sanitize when we must, and encode like our apps depend on it. ‘Cause they do.

Key Takeaway

  • Always validate and sanitize user input to block malicious data before it reaches your app.
  • Encode output properly in HTML, JavaScript, and URLs to prevent browsers from executing injected scripts.
  • Use security headers like Content Security Policy and secure cookie flags to add extra layers of defense.

Understanding Cross-Site Scripting in ASP.NET Core

We’ve seen XSS show up in strange places—places we didn’t expect, like innocuous-looking comment fields or third-party integrations that got a little too trusting. The threat becomes real fast. One script tag, one bad payload, and someone’s credentials could be gone. That’s the essence of XSS: it tricks browsers into executing malicious JavaScript by injecting it through inputs we thought were safe. (1)

There’s no single flavor either. XSS has different personalities:

  • Stored XSS (Subject: attacker; Predicate: stores; Object: script in database)
  • Reflected XSS (Subject: server; Predicate: reflects; Object: script in response)
  • DOM-Based XSS (Subject: frontend; Predicate: modifies; Object: page using user input)

We don’t get to choose which one tries to hit us. So, we’ve gotta prepare for all three.

Sometimes it sneaks through APIs. Other times, it’s buried in headers or logs. We’ve even seen it sit dormant in a database for weeks before being served to the right victim. That’s why we approach XSS prevention like a layered onion—block it at every layer, every step.

Input Validation in ASP.NET Core

We catch problems early. That’s always the goal. We validate user input before it ever touches our logic or our database. ASP.NET Core gives us strong tools to do this, and we lean on them hard.

The model validation attributes aren’t fancy, but they work:

  • [Required]: Won’t let a field go empty.
  • [MaxLength(300)]: Stops bloated inputs.
  • [RegularExpression(@”\w+@\w+\.\w{2,3}”)]: Makes sure input looks like an email.

We use those everywhere we can. It’s not just about rejecting junk—it’s about reducing surface area. If a user’s trying to smuggle in a <script> tag, a good regex will catch it before we have to worry about sanitizing.

That said, sometimes we do want to allow HTML. Think blog editors or support tools. That’s where we sanitize instead of reject.

Sanitizing Rich Text Inputs

We’ve worked with sanitizers that scrub inputs clean—stripping out <script>, onload, javascript: links, and dangerous attributes while keeping things like <b> and <i>. That way, users can still make things bold, but they can’t sneak in a keylogger.

Here’s what we always clean out:

  • <script>, <iframe>, <embed>, <object>
  • on* attributes like onclick, onerror, onload
  • javascript: URLs or data: schemes
  • CSS expressions or style tags

We sanitize on input and encode on output. Double defense.

Output Encoding: The Core of XSS Prevention

Credits: Biswa Ranjan

Sometimes, despite all our validation, something slips past. Maybe a third-party system sent us tainted data. Maybe someone found an obscure input we missed. That’s why we always encode our output.

Encoding means we take dangerous characters and turn them into safe equivalents:

  • < becomes &lt;
  • > becomes &gt;
  • ” becomes &quot;
  • ‘ becomes &#x27;

If we don’t, browsers might treat a value like a tag.

ASP.NET Core helps here. Razor pages encode automatically when we use @Model.Property. That saves us from ourselves, most of the time.

Where We Encode Matters

We encode differently depending on where the data’s going:

  • In HTML? HTML-encode.
  • In attributes? Attribute-encode.
  • In JavaScript? JavaScript-encode (watch out for quotes and backslashes).
  • In URLs? URL-encode.

It’s easy to forget. One time, we injected data into a script block without encoding it properly. Someone managed to break out of the string and inject their own function call. That taught us fast: always encode before you output.

Watch Out for Html.Raw()

Sometimes Razor devs get fancy and use Html.Raw(). That’s fine for static content or safe, trusted strings. But for anything even slightly user-generated, it’s a trap. We stopped using it completely for dynamic content.

Using Razor Tag Helpers Correctly

We rely on Razor tag helpers because they handle encoding by default. That means we’re safer by default too. But if we write our own tag helpers, or use third-party components, we have to double-check that encoding isn’t skipped.

Tag helpers that inject raw HTML or JavaScript? We inspect their source or test them in a sandbox. We never assume they’re safe just because they’re popular.

Configuring Content Security Policy (CSP)

We’ve had a lot of luck using Content Security Policy headers to cut off whole classes of attacks.

With CSP, we can tell the browser what’s allowed to load. Nothing else.

For example:

csharp

CopyEdit

app.Use(async (context, next) =>

{

    context.Response.Headers.Add(“Content-Security-Policy”, “default-src ‘self’; script-src ‘self’;”);

    await next();

});

This means no scripts can run unless they came from us. No CDNs, no inline scripts, no nonsense.

Some of us disable unsafe-inline and eval entirely. It breaks some things at first, but it forces better coding. Cleaner separation. Less risk.

We had to whitelist some trusted domains—for analytics or fonts—but we kept that list tight.

Setting Secure Cookie Flags

XSS sometimes targets cookies, especially session cookies. If an attacker can steal a session ID, they can impersonate a user.

That’s why we always set:

  • HttpOnly: blocks JavaScript from reading the cookie
  • Secure: sends cookie over HTTPS only
  • SameSite=Strict or Lax: limits cross-site requests

csharp

CopyEdit

services.ConfigureApplicationCookie(options =>

{

    options.Cookie.HttpOnly = true;

    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

    options.Cookie.SameSite = SameSiteMode.Strict;

});

We tested this with dev tools—just to see if cookies showed up in document.cookie. They didn’t. That’s the point.

Anti-Forgery Tokens as Bonus Defense

Anti-Forgery Tokens as Bonus Defense

Even though they’re built for CSRF, anti-forgery tokens help tighten things up in general. Every form submission has to include the token. If the token’s wrong or missing, the request fails.

That means a successful XSS injection can’t post forms unless it also grabs a valid token. Hard to do with HttpOnly and CSP in place.

In Razor, we just use @Html.AntiForgeryToken() inside forms and add [ValidateAntiForgeryToken] on actions.

We’ve even used IAntiforgery manually in APIs to inject and validate tokens on AJAX requests. It’s worth the effort.

Dangerous Coding Habits to Break

We’ve all been there. You’re debugging something and disable output encoding to “see what happens.” Or you bypass validation because it’s annoying. But those shortcuts add up—and they open holes.

Here’s a short list of things we stopped doing:

  • No more Html.Raw() with dynamic data
  • No JavaScript that appends innerHTML with user content
  • No inline event handlers (e.g., onclick=)
  • No third-party plugins that aren’t code-reviewed

If something smells off, we sandbox it. If we can’t sandbox it, we don’t ship it.

Tools and Testing for XSS Weaknesses

We test constantly. Before release. After every deployment. When we refactor. (1)

Our toolkit includes:

  • Static code analysis (to catch dangerous APIs)
  • Dynamic scanning (to simulate attacks)
  • Manual tests (to try weird edge cases)
  • CSP violation reports (sent to a test endpoint)

We once found a reflected XSS flaw from a query parameter that bypassed validation due to a model binding mismatch. We caught it during a penetration test before launch. That saved us weeks of post-launch cleanup.

Checklist: What We Always Do

When we develop a new feature, we ask ourselves:

  • Are we validating all inputs?
  • Are we encoding all outputs in the right context?
  • Are cookies HttpOnly, Secure, and SameSite?
  • Is CSP in place and strict enough?
  • Are forms protected with anti-forgery tokens?
  • Are we avoiding risky APIs?

If we can’t say yes to all of these, we don’t ship.

FAQ

What is cross site scripting XSS and why should I worry about it?

Cross site scripting XSS happens when bad actors inject harmful code into your website that runs in other users’ browsers. This lets attackers steal passwords, personal information, or take control of user accounts. ASP.NET Core helps prevent cross site scripting XSS attacks automatically.

How does ASP.NET Core automatically prevent cross site scripting XSS attacks?

ASP.NET Core has built-in protection that automatically escapes HTML content when you display user data. This means dangerous scripts get turned into harmless text instead of running code. The framework treats all user input as potentially unsafe by default.

When should I use Html.Raw() and how can it create XSS vulnerabilities?

Only use Html.Raw() when you absolutely trust the content, like displaying HTML from your own admin panel. Never use Html.Raw() with user input because it bypasses ASP.NET Core’s automatic protection against cross site scripting XSS attacks, making your site vulnerable.

What’s the difference between encoding and sanitizing to prevent cross site scripting XSS?

Encoding converts dangerous characters into safe text that browsers display but don’t execute. Sanitizing removes or cleans harmful parts from user input. ASP.NET Core uses encoding by default to prevent cross site scripting XSS, which is usually safer than sanitizing.

How do I safely display user-generated content in my ASP.NET Core application?

Always let ASP.NET Core’s automatic encoding handle user content. Use Razor syntax like @Model.UserComment instead of Html.Raw(). For rich text content, use a trusted HTML sanitization library that removes dangerous scripts while keeping safe formatting.

Can Content Security Policy headers help prevent cross site scripting XSS in ASP.NET Core?

Yes, Content Security Policy headers tell browsers which scripts they can run and where they can load resources from. Add CSP headers in ASP.NET Core to create an extra layer of protection against cross site scripting XSS attacks.

How do I validate and sanitize form input to prevent cross site scripting XSS?

Use ASP.NET Core’s built-in validation attributes and model binding, which automatically handle basic protection. For additional safety, validate input length, allowed characters, and format. Never trust client-side validation alone to prevent cross site scripting XSS attacks.

What are the best practices for preventing cross site scripting XSS in ASP.NET Core applications?

Always encode output, never use Html.Raw() with user data, validate all input, use Content Security Policy headers, keep ASP.NET Core updated, and regularly test your application for vulnerabilities. Trust ASP.NET Core’s built-in protection and avoid bypassing it unnecessarily.

Conclusion

XSS feels like an old problem. But it keeps coming back because bad habits are hard to kill, and new features sneak in without proper guardrails. We stay sharp by assuming every input is hostile and every output is a potential vector.

Don’t trust data. Don’t skip encoding. Don’t get lazy with headers.

If something feels risky, it probably is. And if something doesn’t feel risky—that’s when we check again. ASP.NET Core gives us tools. It’s our job to use them well.

Want to build secure software with confidence?
Join the Secure Coding Practices Bootcamp — a hands-on course that skips the jargon and gives you real-world skills to write safer code from day one.

Related Articles

References

  1. https://learn.microsoft.com/en-us/aspnet/core/security/cross-site-scripting?view=aspnetcore-9.0
  2. https://pmc.ncbi.nlm.nih.gov/articles/PMC10799887/
Avatar photo
Leon I. Hicks

Hi, I'm Leon I. Hicks — an IT expert with a passion for secure software development. I've spent over a decade helping teams build safer, more reliable systems. Now, I share practical tips and real-world lessons on securecodingpractices.com to help developers write better, more secure code.