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.
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 |
When Cookie Authentication Wins
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=StrictorSameSite=Laxremoves most CSRF attack surface. Combined withHttpOnly, 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
SecurityStampor 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:
Browser-based SPA on the same origin โ cookie authentication (HttpOnly, SameSite=Lax)
Mobile clients and third-party integrations โ JWT bearer (short-lived access tokens + refresh token rotation)
M2M service accounts โ JWT bearer with client credentials (see ASP.NET Core Authorization Strategies: RBAC vs. ABAC vs. Policy-Based โ Enterprise Decision Guide for how authorization layers on top of either scheme)
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
What is the main difference between cookie authentication and JWT in ASP.NET Core?
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.
Can I use both cookie authentication and JWT bearer authentication in the same ASP.NET Core app?
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.
Is JWT more secure than cookie authentication in ASP.NET Core?
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.
Does cookie authentication work for mobile apps in ASP.NET Core?
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.
What is the recommended token lifetime for JWT in ASP.NET Core production APIs?
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.






