🛡️ Security in .NET: Preventing CSRF (Cross-Site Request Forgery)

Imagine a user is logged into your application.

They open a malicious "cat video" website in another tab.

That website contains a hidden form which silently sends a POST request to your API:

POST /api/account/delete

Because the user is authenticated, their browser automatically includes the session cookie.

Your API receives a valid request from an authenticated user and executes it.

The user never intended this action.

This is called Cross-Site Request Forgery (CSRF).


1️⃣ The Defense: Anti-Forgery Tokens

The most widely used protection mechanism is the Synchronizer Token Pattern.

The idea is simple:

  1. The server generates a unique secret token.
  2. The token is sent to the client.
  3. The client must include the token in every state-changing request.
  4. The server validates the token before processing the request.

Since a malicious website cannot read tokens from another domain, forged requests fail validation.


2️⃣ Professional Implementation in ASP.NET Core

ASP.NET Core provides built-in support for CSRF protection via the Antiforgery service.

This integrates seamlessly with Single Page Applications such as Angular or React.


Step A: Configure the Antiforgery Service

In Program.cs, configure the header name used by the frontend:

builder.Services.AddAntiforgery(options =>
{
    options.HeaderName = "X-XSRF-TOKEN";
});

The frontend must send this header with every POST, PUT, PATCH, or DELETE request.


Step B: Automatically Validate Requests

Instead of decorating every controller action with attributes, we register a global filter.

builder.Services.AddControllers(options =>
{
    options.Filters.Add(
        new AutoValidateAntiforgeryTokenAttribute()
    );
});

This automatically validates anti-forgery tokens for:

  • POST requests
  • PUT requests
  • PATCH requests
  • DELETE requests

Step C: Token Provider Middleware

Since APIs are typically stateless, the frontend needs a secure way to retrieve the token.

We create middleware that sends the token as a cookie.

app.Use((context, next) =>
{
    var antiforgery =
        context.RequestServices
        .GetRequiredService<IAntiforgery>();

    var tokens =
        antiforgery.GetAndStoreTokens(context);

    context.Response.Cookies.Append(
        "XSRF-TOKEN",
        tokens.RequestToken!,
        new CookieOptions
        {
            HttpOnly = false,
            Secure = true,
            SameSite = SameSiteMode.Strict
        });

    return next(context);
});

3️⃣ Why This Protection Works

This implementation leverages multiple browser security mechanisms:

Mechanism Purpose
Same-Origin Policy Prevents malicious sites from reading cookies
XSRF Token Cookie Stores the secret token for JavaScript access
X-XSRF-TOKEN Header Sends the token back to the server
Token Validation Ensures request originates from trusted frontend

🔍 Cookie Configuration Explained

Setting Reason
HttpOnly = false Allows JavaScript frameworks to read the token
Secure = true Ensures cookie is sent only over HTTPS
SameSite = Strict Blocks cookie transmission from other origins

4️⃣ Frontend Example (Axios Interceptor)

Frontend frameworks typically read the cookie and send the token automatically:

axios.interceptors.request.use(config => {

    const token =
        document.cookie
        .split('; ')
        .find(row => row.startsWith('XSRF-TOKEN'))
        ?.split('=')[1];

    if (token) {
        config.headers['X-XSRF-TOKEN'] = token;
    }

    return config;
});

📊 Security Coverage Summary

Threat Protection
XSS Prevents script injection
CSP Restricts resource execution
CSRF Prevents unauthorized actions

🏁 Final Thoughts

CSRF protection ensures that authenticated users only perform actions they explicitly intend.

Combined with:

  • Input Sanitization
  • Content Security Policy
  • Secure Cookies
  • Unified API Responses

your application achieves a strong defense-in-depth architecture.

🛡️ Security Principle: Authentication verifies who the user is. CSRF protection verifies who initiated the request. Both are required for a secure API.

Comments

Popular posts from this blog

Promises in Angular

Debouncing & Throttling in RxJS: Optimizing API Calls and User Interactions

Comprehensive Guide to C# and .NET Core OOP Concepts and Language Features