Skip to main content

Command Palette

Search for a command to run...

ASP.NET Core: Cross-Site Request Forgery (CSRF)

Published
4 min read

Cross-Site Request Forgery (CSRF) is an attack where a malicious website tricks an authenticated user into performing an unwanted action on a trusted site where the user is currently logged in.

Because browsers automatically send cookies (including session/authentication cookies) with every request to a specific domain, the server cannot distinguish if the request was initiated by the user willingly or forced by a malicious script.

Real-World Analogy

Imagine you are a bank teller. You have a rule: "If I see a valid ID badge, I process the transfer."

  1. A customer (User) walks up to your counter with their ID badge visible (Logged in).

  2. A stranger (Attacker) stands behind them and whispers, "Transfer $1,000 to the stranger," into the customer's ear.

  3. The customer inadvertently repeats it.

  4. You process the transfer because the ID badge is valid. You didn't verify who initiated the command, only that the person holding the ID badge sent it.

How the Attack Works (Step-by-Step)

  1. Login: The user logs into legit-bank.com. The bank issues an Authentication Cookie to the user's browser.

  2. Trap: Without logging out, the user visits evil-site.com (perhaps via a phishing email).

  3. The Forge: evil-site.com contains a hidden script or form that automatically submits a POST request to legit-bank.com/transfer?amount=1000&to=Attacker.

  4. Execution: The browser sees the request is for legit-bank.com and automatically attaches the Authentication Cookie.

  5. Failure: The bank server receives the request, sees the valid cookie, and processes the transfer, thinking the user intended to do it.

How to Prevent CSRF in .NET Core

.NET Core uses the Synchronizer Token Pattern to prevent this.

The Solution Mechanism

  1. The Token Pair: When the server renders a form, it generates two tokens:

    • Cookie Token: Sent as an HTTP-only cookie.

    • Request Token: Placed as a hidden field in the HTML form.

  2. The Verification: When the user submits the form, the server checks if both tokens match.

  3. The Defense: The malicious site cannot read the Cookie Token (due to same-origin policy) and therefore cannot fabricate the matching Request Token for the form. If the tokens are missing or don't match, .NET Core rejects the request.

Implementation in .NET Core (MVC & Razor Pages)

1. Service Configuration (Program.cs)

In .NET Core 2.0+, this is enabled by default. However, you can customize it in your startup configuration if needed.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

// Optional: Customize Antiforgery options
builder.Services.AddAntiforgery(options =>
{
    options.FormFieldName = "__RequestVerificationToken";
    options.HeaderName = "X-CSRF-TOKEN";
    options.SuppressXFrameOptionsHeader = false;
});

2. The View (Front-end)

If you use the standard <form> tag helper, .NET Core automatically injects the hidden token field for you.

<form asp-controller="Account" asp-action="Transfer" method="post">
    <button type="submit">Transfer</button>
</form>

<form action="/Account/Transfer" method="post">
    @Html.AntiForgeryToken()
    <button type="submit">Transfer</button>
</form>

3. The Controller (Back-end)

You must decorate your Actions (or the entire Controller) with the [ValidateAntiForgeryToken] attribute. This forces the server to check for the token before running the code.

[HttpPost]
[ValidateAntiForgeryToken] // <--- Crucial line
public IActionResult Transfer(TransferModel model)
{
    // If the token is invalid, the request never reaches this line.
    // Logic to transfer money
    return View();
}

Advanced: Global & API Protection

Instead of adding the attribute to every single POST method, you can apply it globally. This is safer as you can't "forget" to add it.

builder.Services.AddControllersWithViews(options =>
{
    // Applies validation to all unsafe HTTP methods (POST, PUT, DELETE)
    // automatically ignores GET, HEAD, OPTIONS, TRACE
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

Note: If you do this, you can use [IgnoreAntiforgeryToken] on specific endpoints that don't need protection (like public hooks).

Option B: AJAX / SPAs (React, Angular, Vue)

Single Page Applications don't use traditional HTML forms. For these, you must send the token in the Request Header.

  1. Server Side: Configure the header name.

     builder.Services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
    
  2. Client Side (JavaScript): Read the token from the cookie or a hidden input and send it in the header.

     fetch('/api/transfer', {
         method: 'POST',
         headers: {
             'Content-Type': 'application/json',
             'X-XSRF-TOKEN': document.getElementsByName('__RequestVerificationToken')[0].value
         },
         body: JSON.stringify({ amount: 1000 })
     });
    

Summary Checklist

  • Action: Add [ValidateAntiForgeryToken] (or [AutoValidateAntiforgeryToken]) to your POST/PUT/DELETE controllers.

  • View: Ensure <form> tags are using asp-action tag helpers OR include @Html.AntiForgeryToken().

  • Verification: Try to make a POST request using Postman to your endpoint without the token. It should return 400 Bad Request.

More from this blog

.

.Net Core

32 posts