Skip to main content

Command Palette

Search for a command to run...

Cookie Authentication vs JWT in ASP.NET Core: Which Should Your API Use?

Updated
โ€ข12 min read
Cookie Authentication vs JWT in ASP.NET Core: Which Should Your API Use?

Cookie authentication and JWT bearer tokens are both first-class citizens in ASP.NET Core's authentication middleware, and both can secure an API. The problem is that teams regularly pick one without fully understanding the trade-offs, and the consequences show up later โ€” session state scaling problems in a horizontally distributed cluster, CSRF vulnerabilities in browser-facing endpoints, or revocation gaps in token-based flows. In ASP.NET Core 10, both mechanisms are well-supported, mature, and genuinely suitable for the right use case. The decision is about matching the mechanism to the client type, deployment topology, and security requirements.

For teams who want to go deeper โ€” full working implementations of JWT authentication with refresh token rotation, storage strategies, and revocation โ€” the complete, annotated source code is on Patreon, including edge cases you won't find in tutorials.

Understanding Chapter 7 of the Zero to Production course walks through JWT access tokens and refresh tokens inside a full production ASP.NET Core API โ€” with the entire auth pipeline wired together, including the logout and rotation flows, so you can see how each decision fits the wider system.

ASP.NET Core Web API: Zero to Production

What Each Mechanism Actually Does

Before comparing, it helps to be precise about what each mechanism is.

Cookie authentication stores an encrypted session ticket โ€” serialised as a ClaimsPrincipal โ€” inside an HTTP-only, Secure, SameSite cookie. The server issues the cookie on login. ASP.NET Core deserialises the ticket on every subsequent request without needing to hit a database, because the session state is encoded in the cookie itself. Revocation requires either a short ExpireTimeSpan, a server-side block list, or sliding expiration tied to some external state.

JWT bearer authentication validates a signed token sent in the Authorization: Bearer header. The token is stateless: once signed and issued, the server only needs the signing key (or public key for RS256/ES256) to validate it. There is no cookie, no session, no round-trip to a session store. Revocation is the hard problem โ€” the token is valid until it expires unless you implement a separate revocation store.

Both are handled by AddAuthentication in Program.cs and integrated into the same [Authorize] pipeline.

Head-to-Head Comparison

Dimension Cookie Authentication JWT Bearer
Transport Automatically sent by browser in every request to the same origin Must be attached manually by client code (Authorization header)
Storage Managed by the browser; HttpOnly means JavaScript cannot read it Must be stored by the client โ€” localStorage, sessionStorage, or memory
CSRF risk Yes โ€” browser auto-sends the cookie; requires antiforgery tokens No โ€” bearer header is never auto-sent by the browser
XSS risk Lower โ€” HttpOnly blocks JavaScript access Higher โ€” token stored in localStorage is accessible to JS
Revocation Straightforward โ€” set a short ExpireTimeSpan or maintain a server-side block list Hard โ€” token is valid until expiry; requires a revocation store for immediate invalidation
Horizontal scaling Stateless (ticket is in the cookie) โ€” no sticky sessions needed with ASP.NET Core's built-in Data Protection Stateless โ€” no shared session state, any instance validates the token
Cross-domain / mobile Blocked by SameSite policy and third-party cookie restrictions โ€” does not work well across domains or for native mobile apps Works across domains and for any client type โ€” no browser dependency
Token size Small โ€” the encrypted ticket is compact Medium to large โ€” claims are Base64-encoded in the payload; too many claims bloat the header
Rotation Built-in sliding expiration Requires explicit refresh token implementation
Logout precision Exact โ€” delete the cookie Approximate โ€” access token stays valid until expiry; refresh token revocation handles the rest

Cookie authentication is the right choice when:

  • The client is a browser, and your API is same-site or same-origin. Browser-based SPAs calling an API on the same domain (or a subdomain with a matching cookie domain) are the canonical use case. The browser handles cookie storage and transmission โ€” no JavaScript token management required.

  • You want CSRF protection built on SameSite semantics. Setting SameSite=Strict or SameSite=Lax removes most CSRF attack surface. Combined with HttpOnly, the token is inaccessible to JavaScript โ€” a meaningful reduction in XSS blast radius.

  • The application has traditional MVC page rendering alongside API endpoints. If you're running a Razor Pages or MVC app that also exposes APIs for its own front-end, cookie authentication means one auth mechanism for everything.

  • Revocation needs to be immediate. Server-side revocation is trivially implemented by tagging a SecurityStamp or a data-protection key rollover โ€” no waiting for a token to expire.

A meaningful gap in most SERP results for this topic: they don't explain that ASP.NET Core cookie authentication is already stateless in the default configuration. The session ticket is encrypted and embedded in the cookie using Data Protection โ€” there is no server-side session store unless you explicitly configure one. This makes it horizontally scalable without sticky sessions, which surprises many developers who assume cookies imply stateful server sessions.

When JWT Bearer Authentication Wins

JWT bearer authentication is the right choice when:

  • The client is not a browser โ€” or needs to call APIs across different origins. Native mobile apps, desktop apps, server-to-server calls, and third-party integrations cannot use cookies reliably. JWT is the right mechanism here.

  • You are building a public API consumed by multiple clients. A public API does not know who its clients are. JWT provides a standard, client-agnostic mechanism that works without any browser involvement.

  • Cross-domain or cross-origin calls are necessary. Third-party cookie restrictions and SameSite policies make cookie-based auth impractical across domains. JWT has no such constraint.

  • You need a machine-to-machine (M2M) authentication flow. Service accounts, CI/CD pipelines, and backend services authenticating with a client credentials flow issue JWTs from an identity provider โ€” cookies are irrelevant in this context.

  • You want to delegate authentication to an external identity provider using OpenID Connect. The OIDC flow delivers an ID token (JWT) and an access token (JWT or opaque) โ€” the API validates the access token, not a cookie.

The Decision That Most Articles Miss: It's Often Both

The binary framing โ€” "use cookies OR use JWT" โ€” is misleading for many real-world architectures. ASP.NET Core supports multiple authentication schemes simultaneously, and the [Authorize] attribute can target specific schemes or let the framework negotiate.

A common enterprise pattern:

Both schemes are registered via AddAuthentication, each with its own handler. The default scheme determines what happens when [Authorize] is used without specifying a scheme. For API-first applications, a common setup is to configure JWT as the default and add cookie authentication as a named secondary scheme.

For the complete implementation โ€” dual-scheme setup, refresh token rotation with theft detection, and the exact AddAuthentication configuration for an enterprise API โ€” the full source is on Patreon.

Security Trade-offs That Matter in Production

The security comparison is more nuanced than "JWT is stateless so it's better" or "cookies are legacy."

The XSS vs CSRF trade-off is real. Storing JWT in localStorage makes it accessible to any JavaScript on the page. A single XSS vulnerability means the attacker exfiltrates the token and has full API access until expiry. Cookies with HttpOnly prevent this specific attack, but shift the risk to CSRF โ€” which is mitigated by SameSite and antiforgery tokens. Neither approach is universally "more secure" โ€” the right choice depends on the threat model.

Short access token lifetime is the key JWT safeguard. The industry standard is 15 minutes for access tokens. Without revocation infrastructure, the access token window is the attack window. Complement short-lived access tokens with refresh token rotation: each refresh invalidates the previous refresh token, so stolen tokens are detected on the next rotation attempt. Microsoft's guidance on token-based security covers this in depth.

Cookie Data Protection keys need to be shared across instances. In a multi-instance deployment (Kubernetes, App Service scale-out), Data Protection keys must be shared โ€” typically persisted to Redis, Azure Blob Storage, or a database. Without this, instances cannot decrypt each other's cookies and users get logged out on every load-balanced request. This is the stateful footprint that cookies introduce โ€” not session state, but key ring synchronisation.

JWT signing algorithm choice matters. HMAC symmetric keys (HS256) are fast and simple but require the secret to be shared with any service that validates tokens. RSA or ECDSA asymmetric keys (RS256, ES256) allow the identity issuer to keep the private key while sharing only the public key for validation โ€” a significant operational security advantage in multi-service architectures. If you are implementing JWT with refresh tokens, see the detailed walkthrough in Implementing JWT Authentication with Refresh Tokens in ASP.NET Core Web API for the full setup.

Recommendation: Match the Mechanism to the Client

The right authentication mechanism depends on your client type:

Client type Recommended mechanism Rationale
Browser SPA, same-origin API Cookie (HttpOnly, SameSite=Lax) Browser handles storage; HttpOnly blocks XSS token theft; SameSite blocks CSRF
Browser SPA, cross-origin API JWT bearer SameSite restrictions make cookies unreliable cross-origin
Native mobile / desktop JWT bearer No browser cookie jar; bearer header works everywhere
M2M / service accounts JWT bearer (client credentials) No user session; credentials flow produces tokens, not cookies
Mixed (browser + mobile + M2M) Both schemes registered; default to JWT ASP.NET Core supports multi-scheme auth natively
MVC Razor Pages + API Cookie (primary) + optional JWT for API routes Single user-facing auth mechanism; API routes can opt in to JWT

For most greenfield ASP.NET Core APIs in 2026 that serve browser clients, mobile apps, and third-party integrations simultaneously, the pragmatic default is JWT bearer with short-lived access tokens and refresh token rotation. Add cookie authentication as a second scheme if the same application also serves browser-only UI routes. The ASP.NET Core authentication documentation is the authoritative reference for scheme registration and scheme selection policy.

โ˜• Prefer a one-time tip? Buy us a coffee โ€” every bit helps keep the content coming!

FAQ

Cookie authentication stores an encrypted session ticket in an HTTP-only browser cookie managed by the browser, while JWT bearer authentication validates a signed token sent in the Authorization header by the client. Cookies are automatically sent by browsers on every request; JWTs must be explicitly attached by client code. Both are stateless in ASP.NET Core's default implementation โ€” neither requires a server-side session store.

Yes. ASP.NET Core's authentication middleware supports multiple registered schemes simultaneously. You register each scheme separately via AddAuthentication and can configure which is the default. Individual endpoints or controllers can specify a scheme using [Authorize(AuthenticationSchemes = "...")], or you can configure a policy-based approach that auto-negotiates based on the request.

Neither is categorically more secure. They have different threat profiles. JWT in localStorage is vulnerable to XSS token theft. Cookies with HttpOnly prevent JavaScript access but introduce CSRF risk mitigated by SameSite policy and antiforgery tokens. The right choice depends on the client type and threat model. For browser-only applications on the same origin, cookies with strong security flags are often the more secure default. For cross-origin or non-browser clients, JWT is the appropriate mechanism.

How do I revoke a JWT in ASP.NET Core before it expires?

Revocation requires a server-side store โ€” typically a Redis cache or database block list containing revoked token identifiers (the jti claim). On each request, the JWT validation middleware checks whether the token's jti is in the block list. Alternatively, keeping access token lifetimes short (15 minutes) and rotating refresh tokens limits the practical attack window without full revocation infrastructure.

Not reliably. Cookie-based authentication depends on the browser's cookie jar and SameSite behaviour. Native mobile apps do not have a browser cookie jar, so cookie management must be implemented manually in HTTP clients โ€” which is non-trivial and fragile. JWT bearer authentication is the standard for mobile clients. The token is stored in the device's secure storage and sent in the Authorization header on every request.

The industry standard is 15 minutes for access tokens. This limits the attack window if a token is compromised, since there is no server-side session to invalidate. Pair short-lived access tokens with longer-lived refresh tokens (typically 7โ€“30 days) stored server-side, and implement refresh token rotation: each use of a refresh token issues a new one and invalidates the old, enabling theft detection on the next rotation attempt.

Should I use cookies or JWT if I'm building an ASP.NET Core API for a React SPA?

It depends on whether the React SPA and the API share the same origin. If both are served from the same domain (e.g., the API and frontend share example.com), cookie authentication with HttpOnly and SameSite=Lax is the cleaner and more secure choice. If the SPA calls a cross-origin API, JWT bearer is the practical default because SameSite policies will block cross-origin cookie transmission on modern browsers.

More from this blog

C

Coding Droplets

201 posts

Coding Droplets is your go-to resource for .NET and ASP.NET Core development. Whether you're just starting out or building production systems, you'll find practical guides, real-world patterns, and clear explanations that actually make sense.

From beginner-friendly tutorials to advanced architecture decisions. We publish fresh .NET content every day to help you grow at every stage of your career.