Authentication tutorials show you how to configure schemes. Production requires you to choose the right pattern for your architecture. This article provides a decision framework based on security tradeoffs, not feature comparisons.
Common questions this answers
- Should I use cookies or JWTs for my web application?
- When do I need OIDC instead of simple authentication?
- Where should I store tokens in a SPA?
- What is the BFF pattern and when should I use it?
Definition (what this means in practice)
Authentication patterns are strategies for verifying user identity and maintaining authenticated sessions. Cookie authentication stores an encrypted authentication ticket in a cookie (optionally backed by server-side session state). JWT bearer authentication uses stateless tokens. OIDC delegates authentication to an identity provider. Each pattern has different security properties and fits different architectures.
In practice, the choice affects where state lives, what attack surfaces you expose, and how you handle token lifecycle.
Terms used
- Cookie authentication: server issues a cookie containing a session identifier or encrypted ticket; browser sends it automatically.
- JWT (JSON Web Token): self-contained token with claims, signed by the issuer, verified by the recipient.
- Bearer token: a token sent in the Authorization header; the holder is presumed authorized.
- OIDC (OpenID Connect): an identity layer on OAuth 2.0 for delegated authentication.
- BFF (Backend for Frontend): a server-side component that handles authentication on behalf of a SPA.
Reader contract
This article is for:
- Engineers choosing authentication strategies for ASP.NET Core applications.
- Architects evaluating security tradeoffs.
You will leave with:
- A decision matrix for choosing between patterns.
- Threat model considerations for each approach.
- Token storage guidance with security analysis.
This is not for:
- Step-by-step authentication setup tutorials.
- Identity provider configuration guides.
Quick start (10 minutes)
If you need a quick answer:
Verified on: ASP.NET Core (.NET 10).
Use cookie authentication when:
- Building a traditional server-rendered web app.
- The browser is your only client.
- You want strong CSRF mitigations via SameSite and anti-forgery.
Use JWT bearer authentication when:
- Building APIs consumed by mobile apps or third parties.
- Clients cannot handle cookies (non-browser).
- You need stateless authentication across services.
Use OIDC when:
- You have an existing identity provider.
- You need single sign-on across applications.
- You want to externalize identity management.
Use BFF pattern when:
- Building a SPA that needs secure token handling.
- You want cookies for the browser, JWTs for backend services.
The three patterns
Cookie authentication
The server creates an encrypted cookie containing the user's identity claims. The browser stores it and sends it automatically with every request to that domain.
// Program.cs
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/login";
options.LogoutPath = "/logout";
options.ExpireTimeSpan = TimeSpan.FromHours(8);
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
});
JWT bearer authentication
The client obtains a JWT from an issuer and sends it in the Authorization header. The server validates the token signature and extracts claims without server-side session state.
// Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://your-identity-provider";
options.Audience = "your-api";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true
};
});
OIDC authentication
The application redirects to an external identity provider. After authentication, the provider returns tokens. ASP.NET Core handles the protocol automatically.
// Program.cs
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = "https://your-identity-provider";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.ResponseType = "code";
options.SaveTokens = true;
});
Decision matrix
Score each criterion for your project. The pattern with the most favorable scores wins.
| Criterion | Cookie | JWT Bearer | OIDC |
|---|---|---|---|
| Browser-only clients | Excellent | Possible | Excellent |
| Mobile/non-browser clients | Poor | Excellent | Good |
| Stateless requirement | No (session state) | Yes | Depends on tokens |
| Built-in CSRF protection | Mitigations via SameSite + anti-forgery | No (header-based) | Mitigations via cookies + anti-forgery |
| Token revocation | Easy (server-side) | Hard (stateless) | Depends on provider |
| Cross-domain/CORS | Complex | Simple | Complex |
| External identity provider | No | Optional | Required |
| Single sign-on | Manual | Manual | Built-in |
Cookie authentication: security tradeoffs
Advantages
- Automatic CSRF protection: With
SameSite=StrictorSameSite=Lax, cookies are not sent on cross-origin requests. - HttpOnly protection: JavaScript cannot access the cookie, mitigating XSS token theft.
- Server-controlled revocation: Invalidate sessions instantly by clearing server-side state.
- Sliding expiration: Extend sessions automatically for active users.
Threats to consider
- CSRF attacks: Mitigated by SameSite and anti-forgery tokens, but requires correct configuration.
- Session fixation: Regenerate session ID on login.
- Cookie theft via XSS: HttpOnly helps, but XSS can still perform actions as the user.
When to use
- Server-rendered web applications (MVC, Razor Pages).
- Applications where the browser is the only client.
- When you need simple, immediate session revocation.
JWT bearer authentication: security tradeoffs
Advantages
- Stateless: No server-side session storage required.
- Cross-service: Easy to validate across multiple APIs.
- Self-contained: Claims are in the token; no database lookup required.
Threats to consider
- Token storage in browser: localStorage is vulnerable to XSS. Stolen tokens grant full access until expiration.
- No revocation: JWTs are valid until they expire. Revocation requires additional infrastructure (token blacklist, short expiration + refresh tokens).
- Token size: JWTs can become large with many claims, affecting request size.
When to use
- APIs consumed by mobile apps or non-browser clients.
- Microservices that need to validate tokens independently.
- Scenarios where statelessness is a requirement.
OIDC: security tradeoffs
Advantages
- Delegated authentication: Identity management handled by a specialized provider.
- Single sign-on: Users authenticate once across multiple applications.
- Standardized protocol: Interoperability with many identity providers.
Threats to consider
- Complexity: More moving parts mean more potential misconfiguration.
- Provider dependency: Your authentication availability depends on the provider.
- Token handling: You still need to decide where to store tokens.
When to use
- Enterprise applications with existing identity infrastructure.
- Applications requiring SSO across multiple domains.
- When you want to externalize identity management (Azure AD, Okta, Auth0, etc.).
Token storage: cookies vs localStorage
This is one of the most debated topics in web security. Here is the threat model analysis.
localStorage
| Aspect | Analysis |
|---|---|
| XSS vulnerability | High - any XSS can read and exfiltrate tokens |
| CSRF vulnerability | None - tokens are not sent automatically |
| Persistence | Survives page refresh and browser restart |
| Cross-tab access | Yes |
HttpOnly cookies
| Aspect | Analysis |
|---|---|
| XSS vulnerability | Lower - JavaScript cannot read the token |
| CSRF vulnerability | Requires SameSite or anti-forgery tokens |
| Persistence | Configurable via expiration |
| Cross-tab access | Yes |
Recommendation
For browser-based applications, prefer HttpOnly cookies with SameSite protection and anti-forgery on state-changing endpoints. XSS attacks are common; reducing the blast radius of XSS is valuable.
If you must use localStorage (some SPA frameworks assume it), ensure rigorous XSS prevention and consider short token lifetimes with refresh.
Refresh token patterns
Access tokens should be short-lived (minutes to hours). Refresh tokens enable obtaining new access tokens without re-authentication.
Secure refresh token handling
// Store refresh tokens server-side, not in the browser
public class TokenService(AppDbContext db)
{
public async Task<string> CreateRefreshTokenAsync(string userId)
{
var token = new RefreshToken
{
UserId = userId,
Token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)),
ExpiresAt = DateTime.UtcNow.AddDays(7),
CreatedAt = DateTime.UtcNow
};
db.RefreshTokens.Add(token);
await db.SaveChangesAsync();
return token.Token;
}
public async Task<bool> ValidateAndRevokeAsync(string token)
{
var refreshToken = await db.RefreshTokens
.FirstOrDefaultAsync(t => t.Token == token && t.ExpiresAt > DateTime.UtcNow);
if (refreshToken is null) return false;
// Rotate: revoke old token
db.RefreshTokens.Remove(refreshToken);
await db.SaveChangesAsync();
return true;
}
}
Rotation strategy
- Issue a new refresh token with each use.
- Revoke the old refresh token immediately.
- Detect reuse of revoked tokens as a potential compromise signal.
BFF pattern for SPAs
The Backend for Frontend pattern solves the SPA token storage problem by keeping tokens on the server.
How it works
- SPA communicates with its own backend (BFF) using cookies.
- BFF authenticates with the identity provider and stores tokens.
- BFF makes API calls on behalf of the SPA, attaching tokens.
- SPA never sees or stores tokens.
Architecture
[Browser/SPA] <--cookies--> [BFF Server] <--JWT--> [API Services]
|
v
[Identity Provider]
Benefits
- Tokens never exposed to JavaScript.
- Cookie-based CSRF and XSS protections apply.
- Token refresh handled server-side.
When to use
- SPAs that need secure authentication.
- When you cannot accept localStorage token storage risks.
- Applications with strict security requirements.
API Gateway authentication
For microservices, centralizing authentication at the gateway reduces complexity.
Pattern
- API Gateway validates tokens for all incoming requests.
- Internal services trust the gateway (or receive a validated identity claim).
- Services do not individually validate external tokens.
Tradeoffs
| Advantage | Consideration |
|---|---|
| Single point of token validation | Gateway becomes critical path |
| Simplified service configuration | Services must trust gateway |
| Centralized token refresh | Internal network must be secured |
Copy/paste artifact: authentication decision rubric
Authentication Decision Rubric
Project: _________________ Date: _________
1. Who are your clients?
[ ] Browser only -> Cookie or OIDC
[ ] Mobile/native apps -> JWT Bearer
[ ] Mixed -> Consider BFF or multiple schemes
2. Do you need stateless authentication?
[ ] Yes -> JWT Bearer
[ ] No -> Cookie
3. Do you have an external identity provider?
[ ] Yes -> OIDC
[ ] No -> Cookie or JWT (self-issued)
4. Do you need single sign-on?
[ ] Yes -> OIDC
[ ] No -> Any pattern works
5. Is immediate token revocation critical?
[ ] Yes -> Cookie (server-side session) or JWT with revocation infrastructure
[ ] No -> JWT with short expiration
6. Building a SPA?
[ ] Yes, security-critical -> BFF pattern
[ ] Yes, standard -> OIDC with PKCE or BFF
[ ] No -> Cookie or JWT based on other criteria
Result: _______________________
Common failure modes
- Storing JWTs in localStorage without rigorous XSS prevention.
- Long-lived JWTs without refresh token rotation.
- Missing SameSite attribute on authentication cookies.
- Not validating all JWT claims (issuer, audience, expiration).
- Exposing refresh tokens to the browser.
- Using the wrong pattern for the client type (cookies for mobile APIs).
Checklist
- Authentication pattern matches client types (browser, mobile, API).
- Token storage location has threat model justification.
- Cookies use HttpOnly, Secure, and appropriate SameSite.
- JWTs validate issuer, audience, and expiration.
- Refresh tokens are stored securely and rotated.
- Token lifetimes are appropriate for the threat model.
FAQ
Can I use both cookies and JWTs in the same application?
Yes. A common pattern: cookies for browser clients, JWT bearer for API clients. Configure multiple authentication schemes and specify which applies to which endpoints.
How do I revoke a JWT?
JWTs are stateless by design. Options: short expiration times (minutes), refresh token revocation, or a token blacklist (adds state). For immediate revocation needs, consider cookies instead.
Is OIDC overkill for a simple application?
It depends on your identity management needs. If you are managing users in your own database and have a single application, cookie authentication is simpler. If you want SSO or external identity, OIDC is appropriate.
Should I use ASP.NET Core Identity?
ASP.NET Core Identity provides user management (registration, password hashing, etc.). It works with cookie authentication by default. Use it when you need to manage user accounts yourself rather than delegating to an external provider.
What is PKCE and when do I need it?
PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks in public clients (SPAs, mobile apps). Use it when implementing OIDC in any client that cannot securely store a client secret.
How short should JWT expiration be?
Balance security (shorter is better for reducing stolen token impact) against user experience (frequent re-authentication is annoying). Common ranges: 15 minutes to 1 hour for access tokens, days to weeks for refresh tokens.
What to do next
Evaluate your client types and security requirements using the decision rubric. If building a SPA with security requirements, investigate the BFF pattern before committing to localStorage tokens.
For more on ASP.NET Core security, read Security Boundaries for AI-Assisted Development.
If you want help evaluating authentication architecture for your project, reach out via Contact.
References
- ASP.NET Core Authentication Overview
- Cookie Authentication in ASP.NET Core
- ASP.NET Core Data Protection Overview
- Work with SameSite cookies in ASP.NET Core
- Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core
- JWT Bearer Authentication
- OpenID Connect in ASP.NET Core
- OWASP Authentication Cheat Sheet
Author notes
Decisions:
- Recommend cookies for browser-only apps. Rationale: HttpOnly and SameSite provide better XSS/CSRF protection than localStorage tokens.
- Recommend BFF for security-critical SPAs. Rationale: keeps tokens server-side, leveraging cookie security properties.
- Recommend short-lived JWTs with refresh rotation. Rationale: balances statelessness with revocation capability.
Observations:
- Teams that choose JWTs for SPAs often later add BFF when security requirements increase.
- localStorage token storage is a common audit finding.
- Missing SameSite attributes remain a frequent configuration gap.