ASP.NET Core  

CSRF Protection In ASP.NET Core

Introduction

Cross-Site Request Forgery (CSRF) is one of the most common and dangerous attacks on web applications. Unlike SQL injection or XSS, CSRF requires no code injection. The attacker simply tricks a valid user into performing an unwanted action on a site where they are already authenticated.

For example, if a user is logged into an online banking portal, an attacker could trick them into clicking a malicious link that triggers a fund transfer request.

ASP.NET Core provides built-in, highly effective CSRF protection, but only when implemented correctly. This article explains:

  • How CSRF works

  • How ASP.NET Core mitigates CSRF attacks

  • Anti-forgery tokens in Razor, MVC, APIs, and Angular

  • Cookie security best practices

  • Real-world implementation patterns

What Is CSRF?

CSRF occurs when:

  1. A user logs into a legitimate site

  2. The site issues an authentication cookie

  3. The attacker sends the user a crafted link/form

  4. When the user clicks it, the browser automatically attaches the cookie

  5. The server mistakes the request as a legitimate user action

Example malicious form

<form action="https://bank.com/transfer" method="POST">
    <input type="hidden" name="to" value="attacker_account" />
    <input type="hidden" name="amount" value="50000" />
</form>
<script>
    document.forms[0].submit();
</script>

Browsers automatically attach cookies.
That is why CSRF is dangerous.

How ASP.NET Core Blocks CSRF

ASP.NET Core uses anti-forgery tokens, also called XSRF tokens.

The framework generates two values:

  1. A cookie token

  2. A request token (form field or header)

Both must match for the request to be valid.

Browser Form --------------> Includes Anti-Forgery Token
Browser Cookies -----------> Contains Cookie Token

Server Validates:
    Cookie Token + Request Token
    ----------------------------
    If both match → Request is valid
    Else → Request rejected

If the attacker submits the form, they cannot produce the matching request token.

When Do You Need CSRF Protection?

ASP.NET Core CSRF protection is required when:

  • Using cookies for authentication (Identity, CookieAuth, FormsAuth)

  • Using HTML forms (POST/PUT/PATCH/DELETE)

  • Using Razor Pages or MVC with login sessions

You do not need it when:

  • Using JWT bearer tokens

  • Using header-based authentication (e.g., API keys)

  • Using same-origin single-page apps with token-based auth

CSRF Protection In Razor Pages And MVC

The simplest case.

ASP.NET Core automatically validates anti-forgery tokens for Razor Pages and MVC actions decorated with:

[ValidateAntiForgeryToken]

Example controller action

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult UpdateProfile(UserModel model)
{
    // Safe action
}

The corresponding Razor form must include:

<form method="post">
    @Html.AntiForgeryToken()
    ...
</form>

ASP.NET Core generates a hidden field:

<input name="__RequestVerificationToken" type="hidden" value="<GUID>" />

Enabling Automatic CSRF Validation (Global Filter)

You can enforce CSRF protection globally for all form posts:

services.AddControllersWithViews(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

Now every state-changing request must include anti-forgery token.

CSRF Protection In Web APIs

Web APIs generally do not need CSRF protection unless:

  • They use cookie authentication

  • They serve SPA apps (Angular, React, Vue) using cookies

  • They are part of a multi-domain system

If the API uses JWT tokens, CSRF is not needed.

But if using cookie auth with SPA, then CSRF must be added manually.

CSRF Protection For Angular + ASP.NET Core API

When cookies are used with Angular (not recommended), ASP.NET Core requires XSRF tokens.

Step 1: Configure Anti-Forgery Token Cookie

Program.cs:

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

Step 2: Expose Anti-Forgery Token Endpoint

Create an endpoint to return the token:

[HttpGet("antiforgery/token")]
public IActionResult GetXsrfToken([FromServices] IAntiforgery antiforgery)
{
    var token = antiforgery.GetAndStoreTokens(HttpContext);
    Response.Cookies.Append("XSRF-TOKEN", token.RequestToken!,
        new CookieOptions
        {
            HttpOnly = false, // Accessible to Angular
            Secure = true
        });

    return Ok();
}

Step 3: Angular Interceptor To Add Token Automatically

@Injectable()
export class CsrfInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const token = this.getCookie('XSRF-TOKEN');
    if (token && !req.headers.has('X-XSRF-TOKEN')) {
      req = req.clone({ headers: req.headers.set('X-XSRF-TOKEN', token) });
    }
    return next.handle(req);
  }

  private getCookie(name: string) {
    const matches = document.cookie.match(new RegExp(
      '(?:^|; )' + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + '=([^;]*)'
    ));
    return matches ? decodeURIComponent(matches[1]) : '';
  }
}

CSRF Protection Architecture Flow (SPA + API)

+----------------------+                +-------------------------+
| Angular SPA (Client) |                | ASP.NET Core API Server |
+----------------------+                +-------------------------+
           |                                  |
           | 1. GET /antiforgery/token        |
           |--------------------------------->|
           |                                  |
           |   Set-Cookie: XSRF-TOKEN         |
           |<---------------------------------|
           |                                  |
           | 2. POST /updateProfile           |
           | X-XSRF-TOKEN: <token>            |
           |--------------------------------->|
           |                                  |
           | Validate: Cookie + Header Match  |
           |--------------------------------->|
           |                                  |
           |         Success Response         |
           |<---------------------------------|

SameSite Cookies And CSRF

SameSite cookies prevent browsers from sending cookies along with cross-site requests.

Settings

  • SameSite=Lax (default)
    Safe for most cases

  • SameSite=Strict
    Highest protection but breaks external redirects

  • SameSite=None; Secure
    Required when using external identity providers

ASP.NET Core default defaults

options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

Enhancing CSRF Protection With Additional Defenses

1. Avoid State-Changing GET Requests

Never allow GET to modify data.

Wrong

GET /deleteUser/5

2. Add Double-Submit Cookie Pattern

Send token both in cookie and header.

3. Use Content-Type Restrictions

Your API should accept:

  • application/json
    not

  • application/x-www-form-urlencoded

  • multipart/form-data

These can be submitted cross-domain.

4. Use CORS Restrictions

Never allow:

AllowAnyOrigin + AllowCredentials

This is extremely unsafe.

Correct:

.WithOrigins("https://your-frontend.com")
.AllowCredentials()

Example: Full CSRF-Safe ASP.NET Core Controller

[ApiController]
[Route("api/[controller]")]
public class ProfileController : ControllerBase
{
    private readonly IAntiforgery _antiforgery;

    public ProfileController(IAntiforgery antiforgery)
    {
        _antiforgery = antiforgery;
    }

    [HttpGet("token")]
    public IActionResult Token()
    {
        var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
        return Ok(new { token = tokens.RequestToken });
    }

    [HttpPost("update")]
    [ValidateAntiForgeryToken]
    public IActionResult UpdateProfile(ProfileDto profile)
    {
        return Ok("Profile updated securely.");
    }
}

Common Mistakes Developers Make

  • Using cookie-based auth for SPA apps

  • Not sending token inside header

  • Passing token via query string

  • Allowing CORS from wildcard origins

  • Forgetting CSRF token on AJAX calls

  • Using anti-forgery token on GET endpoints unnecessarily

  • Using SameSite=None without Secure

Best Practices For Production

  1. Use JWT bearer authentication for SPA apps instead of cookies

  2. If cookies are required, always use anti-forgery token

  3. Set cookie flags:

    • HttpOnly=true

    • Secure=true

    • SameSite=Lax

  4. Block all cross-site form submissions

  5. Ensure POST/PUT/PATCH/DELETE require token

  6. Do not allow AllowAnyOrigin + AllowCredentials in CORS

Conclusion

CSRF protection is essential for any ASP.NET Core application using cookies for authentication. The framework provides robust and easy-to-use anti-forgery features, but developers must correctly integrate them into forms, APIs, Angular front-ends, and cookie policies.

By using anti-forgery tokens, SameSite cookies, secure CORS rules, and header-based validation, you ensure that your application remains safe from one of the web’s most subtle and dangerous attacks.