← Back to Articles         
How do you prevent CSRF when cookie authentication is used?

How do you prevent CSRF when cookie authentication is used?

Published on 02 Oct 2026     10 min read Angular
Security

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 appropriate SameSite policy. Angular receives an XSRF request token and sends it in an X-XSRF-TOKEN header 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.

← Back to Articles