How Do You Prevent CSRF When Cookie Authentication Is Used?
When an Angular + ASP.NET Core application uses cookie authentication, CSRF protection is important because the browser automatically sends authentication cookies with eligible requests.
The standard protection is:
Authentication Cookie + Anti-Forgery Token + SameSite/Secure cookie settings
1. What Is the Solution?
Suppose the user logs in and ASP.NET Core creates:
MyAuthCookie
For subsequent requests, the browser can automatically send:
Cookie: MyAuthCookie=...
An attacker may try to exploit this automatic behavior.
To prevent that, the server issues an additional anti-forgery token.
A protected request effectively requires:
Authentication Cookie
+
Anti-Forgery Token
↓
ASP.NET Core validates both
↓
Request allowed
A forged request may contain the authentication cookie, but it should not contain the correct anti-forgery token.
2. Full Simple Sample Program
Let's create:
Angular
↓
ASP.NET Core Web API
↓
Cookie Authentication
+
CSRF/XSRF protection
We will use the conventional names:
XSRF-TOKEN → Cookie containing request token
X-XSRF-TOKEN → HTTP header
ASP.NET Core
Program.cs
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Antiforgery;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// ------------------------------------------------
// Cookie Authentication
// ------------------------------------------------
builder.Services
.AddAuthentication(
CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "MyAuthCookie";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy =
CookieSecurePolicy.Always;
options.Cookie.SameSite =
SameSiteMode.Lax;
options.LoginPath = "/api/account/login";
});
// ------------------------------------------------
// Authorization
// ------------------------------------------------
builder.Services.AddAuthorization();
// ------------------------------------------------
// Anti-Forgery
// ------------------------------------------------
builder.Services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-TOKEN";
options.Cookie.Name =
"XSRF-TOKEN-ANTIFORGERY";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy =
CookieSecurePolicy.Always;
options.Cookie.SameSite =
SameSiteMode.Lax;
});
// ------------------------------------------------
// CORS
// ------------------------------------------------
builder.Services.AddCors(options =>
{
options.AddPolicy("AngularApp", policy =>
{
policy
.WithOrigins("https://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors("AngularApp");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
There are actually two different anti-forgery-related values involved in ASP.NET Core's token mechanism. The cookie configured above is framework state; we'll separately expose the request token that Angular needs.
3. Login Controller
AccountController.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
[ApiController]
[Route("api/[controller]")]
public class AccountController : ControllerBase
{
[HttpPost("login")]
public async Task<IActionResult> Login(
LoginRequest request)
{
// Demo only.
if (request.Username != "admin" ||
request.Password != "Admin@123")
{
return Unauthorized();
}
var claims = new[]
{
new Claim(
ClaimTypes.Name,
request.Username),
new Claim(
ClaimTypes.Role,
"Admin")
};
var identity =
new ClaimsIdentity(
claims,
CookieAuthenticationDefaults
.AuthenticationScheme);
var principal =
new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults
.AuthenticationScheme,
principal);
return Ok(new
{
message = "Login successful"
});
}
}
public record LoginRequest(
string Username,
string Password);
After successful login, ASP.NET Core creates the authentication cookie:
MyAuthCookie
The browser stores it.
4. Create an Endpoint to Issue the XSRF Request Token
Angular needs access to the request token so that it can send it as a header.
AntiforgeryController.cs
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class AntiforgeryController : ControllerBase
{
private readonly IAntiforgery _antiforgery;
public AntiforgeryController(
IAntiforgery antiforgery)
{
_antiforgery = antiforgery;
}
[HttpGet("token")]
public IActionResult GetToken()
{
var tokens =
_antiforgery.GetAndStoreTokens(
HttpContext);
Response.Cookies.Append(
"XSRF-TOKEN",
tokens.RequestToken!,
new CookieOptions
{
HttpOnly = false,
Secure = true,
SameSite = SameSiteMode.Lax
});
return NoContent();
}
}
Notice:
HttpOnly = false
for the XSRF-TOKEN request-token cookie.
Why?
Because Angular needs to read this token so it can copy it into:
X-XSRF-TOKEN: ...
This cookie is not the authentication cookie.
The authentication cookie should remain:
HttpOnly = true
5. Protected Controller
ProductsController.cs
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class ProductsController : ControllerBase
{
private readonly IAntiforgery _antiforgery;
public ProductsController(
IAntiforgery antiforgery)
{
_antiforgery = antiforgery;
}
[HttpGet]
public IActionResult GetProducts()
{
return Ok(new[]
{
new
{
Id = 1,
Name = "Laptop"
},
new
{
Id = 2,
Name = "Mouse"
}
});
}
[HttpPost]
public async Task<IActionResult> CreateProduct(
ProductRequest request)
{
await _antiforgery.ValidateRequestAsync(
HttpContext);
return Ok(new
{
message =
$"Product {request.Name} created"
});
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProduct(
int id)
{
await _antiforgery.ValidateRequestAsync(
HttpContext);
return Ok(new
{
message =
$"Product {id} deleted"
});
}
}
public record ProductRequest(
string Name);
For the simple example, we're explicitly validating:
await _antiforgery.ValidateRequestAsync(
HttpContext);
before performing the state-changing operation.
In a larger application, you'd normally centralize this policy rather than manually repeating it in every action.
6. Angular Configuration
Angular's HttpClient understands the common XSRF cookie/header pattern.
app.config.ts
import {
ApplicationConfig
} from '@angular/core';
import {
provideHttpClient,
withXsrfConfiguration
} from '@angular/common/http';
export const appConfig:
ApplicationConfig = {
providers: [
provideHttpClient(
withXsrfConfiguration({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN'
})
)
]
};
Angular now knows:
Read cookie:
XSRF-TOKEN
Send header:
X-XSRF-TOKEN
7. Angular Authentication Service
auth.service.ts
import {
inject,
Injectable
} from '@angular/core';
import {
HttpClient
} from '@angular/common/http';
import {
switchMap
} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private http =
inject(HttpClient);
private apiUrl =
'https://localhost:7001/api';
login(
username: string,
password: string) {
return this.http.post(
`${this.apiUrl}/account/login`,
{
username,
password
},
{
withCredentials: true
}
).pipe(
switchMap(() =>
this.http.get(
`${this.apiUrl}/antiforgery/token`,
{
withCredentials: true
}
)
)
);
}
}
After login:
Login
↓
Authentication cookie created
↓
Request anti-forgery token
↓
XSRF-TOKEN created
8. Angular Product Service
product.service.ts
import {
inject,
Injectable
} from '@angular/core';
import {
HttpClient
} from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class ProductService {
private http =
inject(HttpClient);
private apiUrl =
'https://localhost:7001/api/products';
getProducts() {
return this.http.get(
this.apiUrl,
{
withCredentials: true
}
);
}
createProduct(name: string) {
return this.http.post(
this.apiUrl,
{
name
},
{
withCredentials: true
}
);
}
deleteProduct(id: number) {
return this.http.delete(
`${this.apiUrl}/${id}`,
{
withCredentials: true
}
);
}
}
9. How the Complete Flow Works
First the user logs in.
Angular
|
| username/password
v
POST /api/account/login
|
v
ASP.NET Core
|
| Validate user
|
v
Authentication Cookie
MyAuthCookie
|
v
Browser
Then Angular obtains the anti-forgery request token:
Angular
|
v
GET /api/antiforgery/token
|
v
ASP.NET Core
|
| Generate anti-forgery tokens
v
XSRF-TOKEN
|
v
Browser
Now Angular performs a state-changing request:
Angular
|
| POST /api/products
|
| Cookie:
| MyAuthCookie=...
|
| Header:
| X-XSRF-TOKEN=...
|
v
ASP.NET Core
|
+----------------------+
| |
Authentication Anti-Forgery
| Validation
| |
+----------+-----------+
|
v
Request allowed
10. What Happens During a CSRF Attack?
Suppose the victim visits:
https://evil.example
The attacker's page attempts to trigger:
POST https://api.example.com/api/products
Depending on cookie policy, the victim's browser might attach an authentication cookie.
But the attacker should not possess the valid anti-forgery request token required in:
X-XSRF-TOKEN: ...
So ASP.NET Core rejects the request.
Conceptually:
Attacker's Website
|
| forged POST
v
Victim's Browser
|
| Auth cookie may be present
|
| X-XSRF-TOKEN
| X
| missing/invalid
v
ASP.NET Core
|
v
Anti-Forgery Validation
|
X
Request rejected
11. Why Is the XSRF Cookie HttpOnly = false?
This can initially look insecure:
HttpOnly = false
But remember that there are different cookies.
Authentication cookie
MyAuthCookie
should be:
HttpOnly = true
Secure = true
Angular does not need to read it.
XSRF request-token cookie
XSRF-TOKEN
is deliberately readable by Angular:
HttpOnly = false
because Angular needs to copy its value into:
X-XSRF-TOKEN
The XSRF token is not the authentication credential.
12. Use SameSite Cookies
Anti-forgery tokens aren't the only protection.
Configure authentication cookies appropriately:
options.Cookie.SameSite =
SameSiteMode.Lax;
or, when your application architecture permits stronger restrictions:
options.Cookie.SameSite =
SameSiteMode.Strict;
SameSite restricts when browsers send cookies in cross-site contexts.
However, don't blindly set Strict; it can interfere with legitimate cross-site authentication/navigation flows.
13. Use Secure
Authentication cookies should normally use:
options.Cookie.SecurePolicy =
CookieSecurePolicy.Always;
which means the cookie is sent only over HTTPS.
Think:
Authentication Cookie
HttpOnly = true
+
Secure = true
+
Appropriate SameSite
+
Anti-Forgery protection
14. Don't Use GET to Modify Data
CSRF protection also depends on correct HTTP semantics.
Never design:
GET /api/products/delete/10
or:
GET /api/transfer?amount=5000
GET should be safe/read-only.
Use:
DELETE /api/products/10
and:
POST /api/transfers
Then require CSRF protection for the state-changing operation when cookie authentication is used.
15. Does CORS Replace CSRF Protection?
No.
Suppose you configure:
policy.WithOrigins(
"https://myapp.example");
That's useful, but:
CORS
≠
CSRF protection
CORS controls browser cross-origin access. Anti-forgery protection proves that the state-changing request came through a flow that possesses the expected anti-forgery token.
Use CORS as another security boundary/configuration, not as a substitute for CSRF defenses.
16. Important Cross-Origin Angular Detail
Suppose Angular runs at:
https://app.example.com
and ASP.NET Core runs at:
https://api.example.com
Cookie behavior, SameSite, CORS, credentials, and Angular's XSRF mechanism all need to be configured consistently.
For credentialed CORS, ASP.NET Core may require:
.WithOrigins(
"https://app.example.com")
.AllowCredentials()
and Angular requests may require:
{
withCredentials: true
}
You cannot combine credentialed CORS with:
AllowAnyOrigin()
as a production shortcut.
Also note that Angular's automatic XSRF header behavior is easiest in a same-origin setup; cross-origin SPA/API deployments can require an explicit design for how the request token becomes available to the Angular origin.
17. Defense-in-Depth
A strong cookie-authenticated Angular + ASP.NET Core application uses several layers:
HTTPS
+
Secure Authentication Cookie
+
HttpOnly
+
Appropriate SameSite
+
Anti-Forgery Token
+
Server-side token validation
+
Correct CORS configuration
+
Authorization
+
XSS protection
One mechanism shouldn't be treated as the entire security solution.
18. Key Points
1. Cookie authentication creates CSRF considerations
Because the browser may automatically send authentication cookies.
2. Use anti-forgery tokens
The state-changing request should effectively require:
Authentication
+
Valid anti-forgery token
3. Angular supports XSRF
A common convention is:
XSRF-TOKEN cookie
↓
Angular
↓
X-XSRF-TOKEN header
4. ASP.NET Core must validate the token
Angular sending a token is not sufficient.
The server must verify it before performing the protected operation.
5. Keep the authentication cookie HttpOnly
Authentication cookie
HttpOnly = true
Angular doesn't need to read it.
6. The XSRF request token can be readable
XSRF-TOKEN
HttpOnly = false
when using the cookie-to-header pattern, because Angular needs to read it.
7. Protect state-changing requests
Particularly:
POST
PUT
PATCH
DELETE
8. SameSite is another important defense
Use an appropriate value based on your application's architecture.
Interview Questions and Answers
1. How do you prevent CSRF with cookie authentication?
Answer:
Use anti-forgery tokens for state-changing requests, configure authentication cookies with appropriate SameSite, Secure, and HttpOnly settings, use HTTPS, and validate the anti-forgery token on the ASP.NET Core server.
2. How does Angular support CSRF protection?
Answer:
Angular HttpClient supports an XSRF cookie-to-header pattern. It can read an XSRF request-token cookie and send the token in an HTTP header for state-changing requests.
3. What are the conventional Angular XSRF names?
Common defaults are:
Cookie:
XSRF-TOKEN
Header:
X-XSRF-TOKEN
They can also be configured explicitly using:
withXsrfConfiguration(...)
4. Should the authentication cookie be HttpOnly?
Yes.
Authentication Cookie
→ HttpOnly = true
Angular normally doesn't need direct JavaScript access to the authentication cookie.
5. Why might the XSRF token cookie not be HttpOnly?
Because in the cookie-to-header pattern, Angular needs to read the request token and send it as an HTTP header.
It is an anti-forgery token, not the user's authentication credential.
6. Does SameSite completely replace anti-forgery tokens?
Don't assume so.
SameSite is an important defense, but anti-forgery tokens provide an additional explicit validation mechanism for sensitive state-changing operations.
7. Does CORS prevent CSRF?
No.
CORS and CSRF protection solve different problems. A restricted CORS policy should not be treated as a replacement for anti-forgery validation.
8. Which requests should be protected?
State-changing operations, particularly:
POST
PUT
PATCH
DELETE
GET endpoints should not change application state.
Interview Quick Answer
When Angular + ASP.NET Core uses cookie authentication, I prevent CSRF using ASP.NET Core anti-forgery tokens. The authentication cookie is configured with
HttpOnly,Secure, and an appropriateSameSitepolicy. Angular receives an XSRF request token and sends it in anX-XSRF-TOKENheader for state-changing requests, and ASP.NET Core validates that token before processing the operation. CORS and HTTPS provide additional protection but do not replace anti-forgery validation.