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:
A user logs into a legitimate site
The site issues an authentication cookie
The attacker sends the user a crafted link/form
When the user clicks it, the browser automatically attaches the cookie
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:
A cookie token
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:
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
Use JWT bearer authentication for SPA apps instead of cookies
If cookies are required, always use anti-forgery token
Set cookie flags:
HttpOnly=true
Secure=true
SameSite=Lax
Block all cross-site form submissions
Ensure POST/PUT/PATCH/DELETE require token
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.