Anti-Forgery Tokens and Security Headers

Web applications face a class of attacks that are independent of the language or framework: CSRF (Cross-Site Request Forgery), clickjacking, MIME sniffing, and XSS (Cross-Site Scripting). HTTP security headers are the browser-enforced defence โ€” they instruct browsers to refuse certain behaviours. A Content-Security-Policy header tells the browser which scripts are allowed to run. X-Frame-Options prevents your site from being embedded in an iframe for clickjacking. X-Content-Type-Options: nosniff prevents MIME-type sniffing attacks. Adding these headers costs nothing and eliminates entire vulnerability categories.

Security Headers Middleware

// โ”€โ”€ Custom middleware for security headers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public class SecurityHeadersMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context)
    {
        // Prevent MIME type sniffing
        context.Response.Headers["X-Content-Type-Options"]    = "nosniff";

        // Prevent clickjacking โ€” disallow embedding in iframes
        context.Response.Headers["X-Frame-Options"]           = "DENY";

        // XSS protection (legacy header for older browsers)
        context.Response.Headers["X-XSS-Protection"]          = "1; mode=block";

        // Control referrer information sent with requests
        context.Response.Headers["Referrer-Policy"]           = "strict-origin-when-cross-origin";

        // Restrict browser features (camera, mic, geolocation)
        context.Response.Headers["Permissions-Policy"]        =
            "camera=(), microphone=(), geolocation=(), payment=()";

        // HSTS (also set via UseHsts middleware โ€” belt and suspenders for API responses)
        if (context.Request.IsHttps)
            context.Response.Headers["Strict-Transport-Security"] =
                "max-age=31536000; includeSubDomains";

        // Content Security Policy โ€” most powerful XSS defence
        // For an API: restrict to self and no inline scripts
        context.Response.Headers["Content-Security-Policy"]   =
            "default-src 'self'; " +
            "script-src 'self'; " +
            "style-src 'self'; " +
            "img-src 'self' data: https:; " +
            "font-src 'self'; " +
            "connect-src 'self'; " +
            "frame-ancestors 'none'; " +  // equivalent to X-Frame-Options: DENY for modern browsers
            "base-uri 'self'; " +
            "form-action 'self'";

        await next(context);
    }
}

// Register early in the pipeline
app.UseMiddleware<SecurityHeadersMiddleware>();
Note: Content Security Policy (CSP) is the most powerful XSS defence โ€” it tells the browser exactly which origins are allowed to load scripts, styles, images, and other resources. Any script from an unlisted source (including injected inline scripts) is refused. For pure Web APIs that return JSON, a strict CSP is straightforward. For server-rendered HTML pages or Angular SPAs served by ASP.NET Core, CSP configuration requires more careful tuning to allow Angular’s build output. Always test your CSP in report-only mode (Content-Security-Policy-Report-Only) before enforcing it.
Tip: Validate your security headers using securityheaders.com (free online scanner) or OWASP ZAP (automated security scanner). Both give you a grade and list missing or misconfigured headers. Run the scanner against your staging environment as part of your release process. An A+ rating from securityheaders.com means all major defensive headers are correctly configured. Many compliance frameworks (PCI-DSS, SOC 2) require documented evidence of security header configuration.
Warning: Do not set X-Frame-Options: DENY if your application legitimately embeds content in iframes (dashboards, embedded widgets). And do not blindly copy-paste CSP headers from examples โ€” an overly restrictive CSP breaks legitimate application functionality (Angular SPA scripts, fonts, API calls). Test every CSP change thoroughly in development and staging before production deployment. Broken CSP in production blocks all users from loading the application.

Anti-Forgery Tokens for Non-API Applications

// โ”€โ”€ For Razor Pages/MVC โ€” CSRF protection is automatic โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// Form posts include an anti-forgery token validated by [ValidateAntiForgeryToken]

// โ”€โ”€ For API-only applications โ€” CSRF is generally not needed โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// Pure JSON APIs using Bearer token (JWT) authentication are not vulnerable
// to CSRF attacks because:
// - Browsers do not auto-send Authorization headers (unlike cookies)
// - CORS policy restricts which origins can make requests
// - Pre-flight OPTIONS requests further restrict cross-origin access
//
// HOWEVER: If your API uses cookie-based authentication (not JWT):
builder.Services.AddAntiforgery(options =>
{
    options.HeaderName    = "X-CSRF-Token";         // Angular sends token in this header
    options.SuppressXFrameOptionsHeader = false;    // keep X-Frame-Options header
});

// Middleware to validate anti-forgery for cookie-auth API requests
app.UseAntiforgery();

Common Mistakes

Mistake 1 โ€” Not adding security headers (easy wins left on the table)

โŒ Wrong โ€” API deployed without any security headers; fails security audits and compliance checks.

โœ… Correct โ€” add the SecurityHeadersMiddleware shown above as a baseline for every ASP.NET Core application.

Mistake 2 โ€” Overly restrictive CSP breaking Angular SPA

โŒ Wrong โ€” CSP blocks Angular’s runtime script loading, breaking the entire application.

โœ… Correct โ€” test CSP in report-only mode first; audit blocked resources; add necessary sources before enforcing.

🧠 Test Yourself

Your ASP.NET Core API uses JWT Bearer token authentication (tokens in Authorization header). Does it need CSRF protection? Why or why not?