Web applications are a primary target for attackers, and one of the easiest ways to strengthen your app’s security is by setting the right HTTP response headers. These headers instruct the browser to enforce security policies that protect against XSS, clickjacking, data leaks, and protocol downgrade attacks.
In this article, we’ll walk through the must-have security headers for every ASP.NET Core app, why they matter, and how to implement them with examples.
Why Security Headers Matter
Without proper headers, browsers may:
Execute untrusted scripts injected by attackers (XSS).
Allow your site to be embedded inside malicious frames (clickjacking).
Expose sensitive URLs to third-party sites.
Downgrade HTTPS requests to HTTP.
By adding just a few lines of middleware, you can mitigate these risks and boost your app’s security rating (try checking your site on securityheaders.com).
Essential Security Headers
1. Strict-Transport-Security (HSTS)
What it does: Forces browsers to always use HTTPS for your domain.
Why: Prevents downgrade attacks where traffic could fall back to insecure HTTP.
ASP.NET Core Example
if (!app.Environment.IsDevelopment())
{
app.UseHsts(); // default max-age=30 days
}
Or with custom options
app.UseHsts(h => h.MaxAge(days: 365).IncludeSubdomains().Preload());
2. Content-Security-Policy (CSP)
What it does: Restricts which sources the browser can load scripts, styles, images, etc.
Why: A powerful defense against cross-site scripting (XSS).
Example
app.Use(async (context, next) =>
{
context.Response.Headers["Content-Security-Policy"] =
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'";
await next();
});
Tip: Start with Report-Only mode to see what would break before enforcing:
context.Response.Headers["Content-Security-Policy-Report-Only"] =
"default-src 'self';";
3. X-Content-Type-Options
What it does: Prevents browsers from MIME-sniffing a response into a different content type.
Why: Blocks attacks where a malicious file is executed as a script.
Example
context.Response.Headers["X-Content-Type-Options"] = "nosniff";
4. X-Frame-Options
What it does: Stops your site from being embedded inside <iframe>
tags.
Why: Prevents clickjacking attacks.
Example
context.Response.Headers["X-Frame-Options"] = "SAMEORIGIN"; // or DENY
5. Referrer-Policy
What it does: Controls how much referrer information (like full URLs) is sent to other sites.
Why: Prevents leaking sensitive query strings or paths.
Example
context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
6. Permissions-Policy (formerly Feature-Policy)
What it does: Controls access to browser features like camera, microphone, and geolocation.
Why: Prevents abuse of powerful APIs by malicious scripts.
Example
context.Response.Headers["Permissions-Policy"] =
"geolocation=(), camera=(), microphone=()";
7. Cross-Origin Resource Policy (CORP)
What it does: Prevents your resources from being loaded cross-origin unless allowed.
Why: Protects against side-channel attacks like Spectre.
Example
context.Response.Headers["Cross-Origin-Resource-Policy"] = "same-origin";
8. Cross-Origin Opener Policy (COOP)
What it does: Ensures browsing context isolation for your site.
Why: Stops cross-window data leaks.
Example
context.Response.Headers["Cross-Origin-Opener-Policy"] = "same-origin";
9. Cross-Origin Embedder Policy (COEP)
What it does: Requires explicit permission for cross-origin resources to be embedded.
Why: Provides stronger isolation for APIs like SharedArrayBuffer.
Example
context.Response.Headers["Cross-Origin-Embedder-Policy"] = "require-corp";
Putting It All Together: Middleware Example
Instead of adding headers one by one, you can centralize them into a custom middleware.
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var headers = context.Response.Headers;
headers["X-Content-Type-Options"] = "nosniff";
headers["X-Frame-Options"] = "SAMEORIGIN";
headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
headers["Permissions-Policy"] = "geolocation=(), camera=(), microphone=()";
headers["Cross-Origin-Resource-Policy"] = "same-origin";
headers["Cross-Origin-Opener-Policy"] = "same-origin";
headers["Cross-Origin-Embedder-Policy"] = "require-corp";
headers["Content-Security-Policy"] =
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'";
await _next(context);
}
}
// Extension method
public static class SecurityHeadersMiddlewareExtensions
{
public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SecurityHeadersMiddleware>();
}
}
And in Program.cs:
var app = builder.Build();
// Apply security headers
app.UseSecurityHeaders();
app.Run();
Best Practices
Enable HSTS only in Production – avoid issues on localhost.
Test your CSP in Report-Only mode first to avoid breaking legitimate scripts.
Use libraries like NetEscapades.AspNetCore.SecurityHeaders for cleaner configuration.
Audit regularly with Mozilla Observatory and securityheaders.com.
Conclusion
By adding these security headers, you’ll significantly reduce the attack surface of your ASP.NET Core apps. They’re lightweight, easy to implement, and enforce critical protections directly in the browser.