Defense in Depth Beyond Login Screens
Securing an ASP.NET MVC application is not a single feature you bolt on -- it's a set of overlapping defenses so that a failure in one layer doesn't immediately compromise the whole system. A team that nails authentication and authorization but ships Razor views with @Html.Raw(userInput) is still vulnerable to stored XSS; one that encodes output correctly but leaves custom errors off in production still leaks stack traces revealing connection strings and internal file paths to any visitor who triggers an unhandled exception. Real security work means treating each layer -- transport, input validation, output encoding, session handling, error handling, and headers -- as independently necessary.
Cricket analogy: It's like a fielding team not relying on just a strong bowling attack; even with great bowlers, sloppy ground fielding and dropped catches (a different layer) still let the opposition score, so every layer of the defense matters independently.
Output Encoding and Preventing XSS
Razor's @ syntax HTML-encodes output by default, which is why @model.Comment is safe even if a user typed <script>alert(1)</script> into a comment box -- it renders as harmless escaped text rather than executing. The danger appears the moment a developer reaches for @Html.Raw() or Html.Raw(ViewBag.SomeHtml) to render 'rich' user content without first sanitizing it through a library like HtmlSanitizer, because that bypasses encoding entirely and lets any embedded <script> or onerror= handler execute in every other user's browser who views that page -- a classic stored XSS vulnerability that can hijack sessions via document.cookie. Content stored from one user and rendered for others is the highest-risk case, since a single unsanitized blog comment or profile bio can compromise every subsequent visitor rather than just the attacker's own session.
Cricket analogy: It's like a scoreboard operator manually retyping a fan's submitted comment onto the big screen without checking it -- if they blindly copy raw text instead of filtering it, an inappropriate message reaches every spectator in the stadium at once.
Transport Security and Secure Headers
RequireHttpsAttribute (or a global filter registering it) forces redirects from HTTP to HTTPS, but HTTPS alone doesn't stop a user from being downgraded on their very first visit before the redirect happens -- that's what the Strict-Transport-Security header (HSTS) solves, instructing browsers to never attempt plain HTTP again for that domain. Beyond transport, headers like X-Content-Type-Options: nosniff prevent browsers from MIME-sniffing a file into executing as script, X-Frame-Options: DENY (or a Content-Security-Policy frame-ancestors directive) prevents clickjacking by blocking the page from being embedded in a malicious iframe, and a properly scoped Content-Security-Policy restricts which script sources the browser will execute at all, providing a second line of defense even if an XSS payload slips past encoding.
Cricket analogy: It's like a stadium not just checking tickets at the gate (HTTPS) but also permanently registering season-ticket holders' devices so future entry attempts skip the insecure old paper-ticket line entirely (HSTS).
Safe Error Handling in Production
The customErrors element in Web.config (or <system.webServer><httpErrors>) should always be set to On or RemoteOnly in production, because the default yellow-screen-of-death exception page dumps the full stack trace, including SQL query text, file system paths, and sometimes connection strings pulled from configuration -- information that dramatically shortens an attacker's reconnaissance phase. The safer pattern pairs customErrors with a global Application_Error handler (or a custom HandleErrorAttribute) that logs the full exception server-side for diagnostics while showing the visitor only a generic friendly error page with no implementation detail.
Cricket analogy: It's like a team's dressing room strategy discussion staying strictly internal, while the press conference outside gives reporters only a polished, generic summary -- the tactical detail (stack trace) never reaches the opposition.
<!-- Web.config -->
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="~/Error/Index">
<error statusCode="404" redirect="~/Error/NotFound" />
<error statusCode="500" redirect="~/Error/Index" />
</customErrors>
<httpCookies httpOnlyCookies="true" requireSSL="true" />
</system.web>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="X-Content-Type-Options" value="nosniff" />
<add name="X-Frame-Options" value="DENY" />
<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
<add name="Content-Security-Policy" value="default-src 'self'; script-src 'self'" />
</customHeaders>
</httpProtocol>
</system.webServer>Apply [RequireHttps] as a global filter in FilterConfig.RegisterGlobalFilters rather than decorating every controller individually -- a single missed controller with a forgotten attribute is enough to accept plain-HTTP traffic.
Never use @Html.Raw() on user-submitted content without running it through a dedicated HTML sanitizer first. Even content you 'trust' because it came from your own database may have been stored unsanitized in the first place, making the raw-output call the actual vulnerability, not the original input.
- MVC security is layered defense -- strong auth doesn't compensate for unencoded output or leaked stack traces.
- Razor's @ syntax HTML-encodes by default; @Html.Raw() bypasses that and is a common source of stored XSS.
- RequireHttpsAttribute plus the Strict-Transport-Security header together close the gap between first-visit and redirected HTTPS.
- X-Content-Type-Options, X-Frame-Options, and Content-Security-Policy headers provide defense in depth beyond encoding alone.
- customErrors should be On or RemoteOnly in production to avoid leaking stack traces, connection strings, and file paths.
- A global Application_Error handler should log full exceptions server-side while showing only a generic friendly page to visitors.
- Global filters (like [RequireHttps]) are safer than per-controller attributes because they can't be accidentally omitted.
Practice what you learned
1. What does Razor's default @ syntax do to output like @model.Comment?
2. What problem does the Strict-Transport-Security (HSTS) header solve that a simple HTTP-to-HTTPS redirect does not?
3. Why is customErrors mode="On" or "RemoteOnly" important in production?
4. What is the primary risk of calling @Html.Raw() on user-submitted content?
5. Why is registering [RequireHttps] as a global filter safer than adding it to each controller individually?
Was this page helpful?
You May Also Like
Authentication in MVC
How ASP.NET MVC applications verify user identity using Forms Authentication, ASP.NET Identity, OWIN middleware, and external login providers.
Authorization and Roles
How ASP.NET MVC restricts access to controllers and actions using the [Authorize] attribute, role-based checks, and custom policy-style filters.
Anti-Forgery Tokens
How ASP.NET MVC prevents Cross-Site Request Forgery (CSRF) using the @Html.AntiForgeryToken helper and the ValidateAntiForgeryToken filter.
Session and State Management
How ASP.NET MVC preserves data across stateless HTTP requests using Session, TempData, ViewData/ViewBag, cookies, and out-of-process session stores.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics