ASP.NET Core JWT Authentication
Implement JWT-based authentication in ASP.NET Core.
A Simple Explanation
The Concert Ticket Analogy
Imagine you go to a concert. The organizer gives you a ticket that says:
- Your name
- Your seat number
- Date and time
- A special signature proving it's real
When you enter, security doesn't call the organizer to check. They just look at the signature and let you through. If someone forges a ticket, the signature won't match, and security rejects it.
JWT works the same way:
- Your server is the concert organizer
- The JWT is the ticket
- Claims (name, user ID, permissions) are the ticket details
- The signature proves the ticket came from your server
- The client (browser/app) is the person at the gate
Why This Matters: Instead of asking the database "Is this user real?" for every request, you trust the signature. It's fast because you don't need database lookups.
Why JWT Exists
The Problem Without JWT
Old approach (session-based):
- User logs in with username/password
- Server creates session, stores in database:
{userId: 123, username: 'john'} - Server sends session ID as cookie
- Client includes cookie in every request
- For every request, server queries database: "Does session ID 'xyz' exist?"
This gets slow with millions of requests.
The Solution With JWT
- User logs in once (verify password)
- Server creates JWT containing user info + signature
- Client stores JWT (in localStorage, secure storage, or cookies)
- Client includes JWT in every request header:
Authorization: Bearer <token> - Server verifies signature only (no database lookup needed!)
- If signature is valid, server trusts the claims inside
Performance Comparison:
- Session: Request → Database query → Response (slow)
- JWT: Request → Verify signature → Response (fast)
With JWTs, one server can handle 10x more users without database bottleneck.
Additional Benefits:
- Stateless: Server doesn't store sessions; scales to multiple servers
- Cross-domain: Works across different domains/APIs
- Expiring tokens: Automatically invalid after time period
- Self-contained: Token carries all info server needs
Content Overview
Table of Contents
- How JWT Works (Step-by-Step)
- JWT Configuration
- Token Generation Service
- Login Endpoint
- Protected Endpoints
- Token Refresh Strategy
- Real-World Use Cases
- Security Best Practices
- JWT Token Structure (Deep Dive)
- Troubleshooting Common Issues
- Related Concepts to Explore
How JWT Works (Step-by-Step)
Step 1: User Logs In
Client → Server: "I'm john with password secret123"
Step 2: Server Verifies & Creates Token
Server checks: Is password correct? ✓
Server creates JWT: {userId: 1, name: "John", role: "user"}
Server signs it with secret key
Server sends back: "Here's your token"
Step 3: Client Stores Token
Client stores in: localStorage or secure storage
Step 4: Client Uses Token in Requests
Client → Server: GET /products with header "Authorization: Bearer <token>"
Step 5: Server Verifies Token
Server: Does signature match? ✓
Server: Is token expired? ✗ (not expired)
Server: Extract userId from token = 1
Server: Return product to user 1
Key Insight: Server never queries database for "Who is this user?" — it trusts the signature.
1. JWT Configuration
What We're Configuring: Before you can use JWT, you need to tell ASP.NET:
- How to verify JWT signatures
- What claims to trust
- When tokens expire
- Who issued the token ("Issuer")
- Who can use it ("Audience")
Program.cs Setup:
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var key = Encoding.ASCII.GetBytes(jwtSettings["SecretKey"]);
builder.Services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = jwtSettings["Issuer"],
ValidateAudience = true,
ValidAudience = jwtSettings["Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
app.UseAuthentication();
app.UseAuthorization();
appsettings.json:
{
"JwtSettings": {
"SecretKey": "your-secret-key-at-least-32-characters-long",
"Issuer": "yourapp.com",
"Audience": "yourapp-users",
"ExpirationMinutes": 60
}
}
Understanding Each Setting:
SecretKey- Random string used to sign tokens. Keep this secret! If leaked, anyone can create fake tokens. Use at least 32 characters (256 bits)Issuer- Who created the token (your app). Server checks: "Did MY app sign this?"Audience- Who should use this token. Server checks: "Is this token for MY users?"ExpirationMinutes- How long the token works. 60 = token dies after 1 hour. After that, user must log in again (or use refresh token)
Configuration Explanation:
ValidateIssuerSigningKey- Check signature matches secret keyIssuerSigningKey- The secret used to sign/verifyValidateIssuer- Check token came from YOUR serverValidateAudience- Check token is FOR YOUR appValidateLifetime- Check token hasn't expiredClockSkew = TimeSpan.Zero- Don't give extra time for expired tokens (strict)
2. Token Generation Service
What This Does: Creates the actual JWT token that gets sent to the client.
Key Concepts:
- Claims - Information inside the token (user ID, name, email, role)
- Expiration - When the token stops working
- Signing - The process of cryptographically signing the token
public interface ITokenService
{
string GenerateToken(User user);
RefreshToken GenerateRefreshToken();
}
public class TokenService : ITokenService
{
private readonly IConfiguration _configuration;
public TokenService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateToken(User user)
{
var jwtSettings = _configuration.GetSection("JwtSettings");
var key = Encoding.ASCII.GetBytes(jwtSettings["SecretKey"]);
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
// Subject = The claims (information about the user)
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), // Unique ID
new Claim(ClaimTypes.Name, user.Username), // Username
new Claim(ClaimTypes.Email, user.Email), // Email
new Claim("role", user.Role) // Custom claim: user role
}),
// When does this token expire?
Expires = DateTime.UtcNow.AddMinutes(
int.Parse(jwtSettings["ExpirationMinutes"])),
// Who issued this token?
Issuer = jwtSettings["Issuer"],
// Who is this token for?
Audience = jwtSettings["Audience"],
// How to sign the token (create signature)
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature) // HMAC-SHA256 algorithm
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token); // Converts to string format
}
public RefreshToken GenerateRefreshToken()
{
// Create a long random string (refresh token doesn't have claims)
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomNumber); // Cryptographically random bytes
}
return new RefreshToken
{
Token = Convert.ToBase64String(randomNumber),
Expires = DateTime.UtcNow.AddDays(7) // Lasts 7 days vs access token's 60 min
};
}
// Note: Refresh tokens are stored in database. Access tokens are not.
}
3. Login Endpoint
What Happens Here:
- User submits username/password
- Server verifies credentials against database
- If valid, server creates two tokens:
- Access Token (short-lived, 60 min) - Used for API requests
- Refresh Token (long-lived, 7 days) - Used to get new access tokens
- Client stores both tokens
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IUserService _userService;
private readonly ITokenService _tokenService;
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
// STEP 1: Verify username and password from database
var user = await _userService.AuthenticateAsync(
request.Username,
request.Password);
if (user == null)
{
return Unauthorized(new { message = "Invalid credentials" });
}
// STEP 2: Generate tokens (doesn't hit database)
var accessToken = _tokenService.GenerateToken(user);
var refreshToken = _tokenService.GenerateRefreshToken();
// STEP 3: Store refresh token in database for revocation support
await _userService.SetRefreshTokenAsync(user.Id, refreshToken);
// STEP 4: Return both tokens to client
return Ok(new
{
accessToken, // Use this for API calls
refreshToken = refreshToken.Token, // Keep this safe
expires = refreshToken.Expires
});
}
[HttpPost("refresh-token")]
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest request)
{
// Client's access token expired, but they have a refresh token
var user = await _userService.GetUserByRefreshTokenAsync(request.RefreshToken);
// Check: Is refresh token valid and not expired?
if (user == null || user.RefreshTokenExpires < DateTime.UtcNow)
{
return Unauthorized(new { message = "Invalid or expired refresh token" });
}
// Create new access token (refresh token stays the same)
var newAccessToken = _tokenService.GenerateToken(user);
return Ok(new { accessToken = newAccessToken });
}
// Real-world: You can optionally rotate refresh tokens (issue new one each refresh)
}
4. Protected Endpoints
How Protection Works:
[Authorize]attribute checks that request includes valid JWT- If no token or invalid signature, request is rejected (401 Unauthorized)
- If token is valid, code runs and can access user info
Role-Based Access:
[Authorize(Roles = "Admin")]only allows admin users- Check is done via the "role" claim in the JWT
[ApiController]
[Route("api/[controller]")]
[Authorize] // All endpoints in this controller require valid JWT
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
// Any authenticated user can read (JWT required, any role works)
var product = await _service.GetProductAsync(id);
if (product == null) return NotFound();
return Ok(product);
}
[HttpPost]
[Authorize(Roles = "Admin")] // Only users with role="Admin" can execute
public async Task<IActionResult> CreateProduct([FromBody] CreateProductRequest request)
{
// If user's JWT doesn't have role=Admin, this returns 403 Forbidden
var product = await _service.CreateProductAsync(request);
return Created($"/api/products/{product.Id}", product);
}
[HttpDelete("{id}")]
[Authorize(Roles = "Admin")] // Admin-only endpoint
public async Task<IActionResult> DeleteProduct(int id)
{
await _service.DeleteProductAsync(id);
return NoContent();
}
// Accessing user info from JWT
[HttpGet("my-info")]
public IActionResult GetMyInfo()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var userName = User.FindFirst(ClaimTypes.Name)?.Value;
return Ok(new { userId, userName });
}
}
5. Token Refresh Strategy
Why Two Tokens?
- Access Token (short-lived: 60 min): Used for API calls. If stolen, attacker has limited time
- Refresh Token (long-lived: 7 days): Used only to get new access tokens. More carefully guarded
User Experience:
Time 0:00 - User logs in
- Gets accessToken (expires 1:00) and refreshToken (expires in 7 days)
Time 0:30 - User browses site
- Uses accessToken for all requests ✓
Time 1:05 - accessToken expired
- App automatically calls /refresh-token with refreshToken
- Gets new accessToken (expires 2:05)
- User doesn't even notice ✓
Time 7:01 - refreshToken expired
- App can't get new accessToken
- User redirected to login page
Benefits:
- Access tokens can have short lifetime (safe)
- Users don't re-authenticate frequently (convenient)
- Refresh tokens rarely transmitted (secure)
- Can revoke refreshes by deleting from database
Client Usage (JavaScript)
// ===== LOGIN =====
const loginResponse = await fetch('https://api.example.com/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'john', password: 'secret' })
});
const { accessToken, refreshToken } = await loginResponse.json();
// Store securely (localStorage is convenient but not most secure)
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
// ===== USE JWT IN REQUESTS =====
const getProduct = async (productId) => {
let token = localStorage.getItem('accessToken');
let response = await fetch(`https://api.example.com/products/${productId}`, {
headers: {
'Authorization': `Bearer ${token}` // Include JWT in header
}
});
// If token expired (401), refresh it
if (response.status === 401) {
const refreshResponse = await fetch('https://api.example.com/auth/refresh-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: localStorage.getItem('refreshToken') })
});
if (refreshResponse.ok) {
const { accessToken: newToken } = await refreshResponse.json();
localStorage.setItem('accessToken', newToken);
// Retry original request with new token
response = await fetch(`https://api.example.com/products/${productId}`, {
headers: { 'Authorization': `Bearer ${newToken}` }
});
}
}
return response.json();
};
// ===== LOGOUT =====
const logout = () => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
// Optional: Call /auth/logout on server to revoke refresh token
};
Security Best Practices
1. Use HTTPS Always
❌ WRONG: http://api.example.com (token visible in transit)
✓ RIGHT: https://api.example.com (encrypted)
Without HTTPS, an attacker can intercept the token from network traffic.
2. Strong Secret Key
❌ WRONG: "mysecret" (too short, guessable)
✓ RIGHT: Generate with: `System.Security.Cryptography.RandomNumberGenerator`
At least 256 bits = 32 bytes = 43 characters in Base64
3. Short Access Token Expiration
❌ WRONG: 30 days (if stolen, attacker has 30 days)
✓ RIGHT: 15-60 minutes (narrow theft window)
4. Use Refresh Tokens for Longevity
Access Token: 60 minutes (for API calls)
Refresh Token: 7 days (for getting new access tokens)
If access token is stolen, it expires in an hour. Refresh tokens are used less frequently and stored more carefully.
5. Store Refresh Tokens in Database
// When user logs in:
await _db.RefreshTokens.AddAsync(new RefreshToken
{
UserId = user.Id,
Token = refreshToken,
Expires = DateTime.UtcNow.AddDays(7)
});
// For logout: DELETE refresh token from database
// Now user can't get new access tokens
6. Secure Token Storage (Client-Side)
// ❌ localStorage (vulnerable to XSS attacks)
localStorage.setItem('token', token);
// ❌ sessionStorage (also vulnerable to XSS)
sessionStorage.setItem('token', token);
// ✓ httpOnly cookies (protected from JavaScript)
// Server sets: Set-Cookie: token=xxx; httpOnly; Secure; SameSite=Strict
// Browser automatically includes in requests
// JavaScript CANNOT read it (protected from XSS)
// ✓ Secure storage (on mobile apps)
// iOS: Keychain
// Android: EncryptedSharedPreferences
7. Verify Signature on Every Request
// ASP.NET Core does this automatically with [Authorize] attribute
// But be explicit about validation settings:
ValidateIssuerSigningKey = true; // Check signature is valid
ValidateIssuer = true; // Check MY server signed it
ValidateAudience = true; // Check it's FOR MY app
ValidateLifetime = true; // Check it's not expired
8. No Sensitive Data in JWT
// ❌ WRONG: Token contains password or credit card
const token = jwt.sign({ password: "secret123" }, key);
// ✓ RIGHT: Token contains only non-sensitive user info
const token = jwt.sign({ userId: 1, role: "user", email: "john@example.com" }, key);
// Token is readable (Base64 encoded, not encrypted)
9. Implement Logout / Revocation
[Authorize]
[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// Delete all refresh tokens for this user
var refreshTokens = _db.RefreshTokens.Where(t => t.UserId == int.Parse(userId));
_db.RefreshTokens.RemoveRange(refreshTokens);
await _db.SaveChangesAsync();
// Now user can't get new access tokens even if they have an old one
return Ok();
}
10. Implement Key Rotation
// Periodically change your secret key
// Keep old keys for validating old tokens (graceful transition)
public IEnumerable<SymmetricSecurityKey> GetValidationKeys()
{
yield return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(currentKey));
yield return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(oldKey)); // Still valid
}
Real-World Use Cases
1. Single Page Application (SPA) - React/Angular/Vue
User navigates to https://myapp.com
↓
JavaScript app loads
↓
User enters credentials in login form
↓
App POSTs to /api/auth/login
↓
Server returns accessToken + refreshToken
↓
App stores in localStorage
↓
User browses the app
↓
Everyone API request includes: Authorization: Bearer <token>
↓
Server validates signature (no database query)
↓
Instant response
Why JWT Shines: No server-side sessions needed. App can serve from CDN (no backend required until API).
2. Mobile App - iOS/Android
User opens app
↓
App checks secure storage for token
↓
If exists and valid, load cached data
↓
If not, show login screen
↓
User enters credentials
↓
App calls /api/auth/login
↓
Server returns accessToken + refreshToken
↓
App stores in EncryptedSharedPreferences (Android) or Keychain (iOS)
↓
All subsequent API calls include JWT
Why JWT Shines: No backend session storage needed. App works offline with cached data. More secure than storing password.
3. Microservices Architecture
Service A (User Service) Service B (Product Service) Service C (Order Service)
↓ ↓ ↓
User logs in → Service A creates JWT
↓
Client stores JWT
↓
Client calls Service B with JWT: GET /products
↓
Service B validates signature (no call to Service A)
↓
Service B extracts userId from JWT
↓
Service B returns products
↓
Client calls Service C with JWT: POST /orders
↓
Service C validates signature
↓
Service C extracts userId from JWT
↓
Service C creates order
Benefit: Services don't depend on each other. Each validates JWT independently.
4. Third-Party Integration
Your Company: emitech.com
Partner Company: acmecorp.com
Partner: "We need to access your API"
You: "Here's a JWT credential: {partner: 'acmecorp', scope: ['read:products']}"
Partner: "We'll include this JWT in requests"
You: "I'll validate signature and check scope"
Partner calls: GET /api/products with JWT
You: Signature valid? ✓ Is this partner allowed? ✓ Return products
Why JWT Shines: No backend session per partner. Credentials don't expire (token is verifiable). Partner can't access other data (scope limited).
5. Mobile Desktop Hybrid
User logs in on Web
↓
Gets JWT stored in httpOnly cookie
↓
User opens Mobile App
↓
App has no JWT yet
↓
App has QR code scanner
↓
User scans QR code on web
↓
QR contains temporary code
↓
App calls /api/login/qr?code=xyz
↓
Server verifies code, returns JWT
↓
App stores securely
↓
Both web and mobile use same JWT for API
6. Progressive Enhancement
Old API (session-based): New API (JWT-based):
/api/products /api/v2/products
Requires server sessions JWT stateless
Slower (DB lookup per request) Faster (signature validation only)
Can't scale easily Scales infinitely
JWT Token Structure (Deep Dive)
A JWT has three parts separated by dots: header.payload.signature
Example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.
dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
↑ ↑ ↑
header payload signature
Part 1: Header (Base64URL Encoded)
{
"alg": "HS256", // Algorithm: HMAC-SHA256
"typ": "JWT" // Type: JSON Web Token
}
Decoded: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Part 2: Payload (Base64URL Encoded - NOT ENCRYPTED!)
{
"sub": "1234567890", // Subject (user ID)
"name": "John Doe", // User name
"iat": 1516239022, // Issued at
"exp": 1516242622, // Expiration
"iss": "yourapp.com", // Issuer
"aud": "yourapp-users" // Audience
}
Decoded: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0
⚠️ Important: Payload is Base64 encoded, NOT encrypted. Anyone can decode it and see the claims. Don't put secrets in JWT.
Part 3: Signature (Cryptographic Hash)
HMAC-SHA256(
base64url(header) + "." +
base64url(payload),
"your-secret-key"
)
Result: dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
How Verification Works:
- Server receives JWT
- Server extracts header and payload
- Server computes signature using same algorithm and secret key
- Server compares computed signature with received signature
- If they match: ✓ Token is valid (not tampered with)
- If they don't match: ✗ Token is invalid (someone forged it)
Why This Works:
- Attacker intercepts token
- Attacker tries to change payload (e.g., change
role: "user"torole: "admin") - Attacker can't recompute signature (don't know secret key)
- Server rejects: signature doesn't match new payload
- Attack fails ✓
You Can Verify Yourself: Visit jwt.io and paste token. You'll see decoded payload. You can't forge a valid token without the secret key.
Troubleshooting Common Issues
Issue 1: "Invalid Token Signature"
Cause: Secret key mismatch
- Server A signs with "secret-key-1"
- Server B tries to validate with "secret-key-2"
Fix: Ensure all servers use same secret key
Issue 2: "Token Expired"
Cause: User's access token is old
Fix: Use refresh token to get new access token:
POST /auth/refresh-token with refreshToken
Issue 3: "Unauthorized 401" on Valid Token
Cause: Multiple reasons
1. Token not in Authorization header: Authorization: Bearer <token>
2. Token format wrong: Bearer TOKEN (need space)
3. Token claims don't match scope (audience, issuer mismatch)
4. Clock skew: Server clock differs from when token was issued
Fix:
- Check header format
- Check appsettings match token's issuer/audience
- Sync server clocks (NTP)
Issue 4: "ClaimTypes.NameIdentifier returns null"
Cause: Claim doesn't exist in JWT
Verify token contains claim:
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId == null) { /* Claim missing */ }
Issue 5: Custom Claims Not Appearing
Wrong: Generated with "role" but reading with "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
Fix: Use consistent claim names
var role = User.FindFirst("role")?.Value; // Match what you put in
Related Concepts to Explore
Authentication & Authorization
- OAuth 2.0 - Industry standard for delegated authorization ("Sign in with Google"). More complex than JWT but supports third-party login
- OpenID Connect - Authentication layer on top of OAuth 2.0. Combines OAuth 2.0 + JWT for complete auth solution
- SAML 2.0 - Enterprise-focused authentication standard (legacy but widely used)
- Kerberos - Network authentication protocol for enterprise environments
Authorization Strategies
- Role-Based Access Control (RBAC) - Simple: roles determine permissions (Admin, User, Guest)
- Claim-Based Authorization - Flexible: permissions based on any claim (department, subscription level, location)
- Policy-Based Authorization - Complex rules: "Allow if (role=Admin AND department=IT)"
- Attribute-Based Access Control (ABAC) - Advanced: permissions based on attributes of user, resource, and context
Token Handling
- Token Revocation - Immediate logout by blacklisting tokens or deleting refresh tokens
- Token Introspection - Query authorization server to verify token validity (slow but secure)
- Token Binding - Bind token to specific IP/device to prevent theft
- Mutual TLS (mTLS) - Both client and server present certificates for stronger authentication
Advanced JWT Concepts
- RS256 / Asymmetric Signing - Use public/private key pair instead of shared secret. Public key for validation, private for signing
- Key Rotation - Regularly change signing keys without invalidating old tokens
- JWE (JSON Web Encryption) - Encrypt JWT payload (not just sign it). For when you need encryption
- JWKS (JSON Web Key Set) - Publish public keys so other services can validate your tokens
Security Concepts
- CORS (Cross-Origin Resource Sharing) - Control which domains can access your API
- CSRF (Cross-Site Request Forgery) - Protection when using cookies with JWTs
- XSS (Cross-Site Scripting) - Why httpOnly cookies are safer than localStorage
- Rate Limiting - Prevent brute force login attacks
- Audit Logging - Log all authentication events for security analysis
Multi-Factor Authentication
- Two-Factor Authentication (2FA) - Require second verification (SMS, authenticator app)
- Multi-Factor Authentication (MFA) - More than 2 factors (something you know, have, are)
- TOTP (Time-based One-Time Password) - Authenticator apps (Google Authenticator)
- WebAuthn / FIDO2 - Passwordless authentication using biometrics or hardware keys
Session Management
- Session Token vs JWT - Session stored server-side, JWT is stateless
- Sliding Expiration - Extend token lifetime with each request
- Concurrent Sessions - Track multiple login locations per user
- Session Fixation - Attack where attacker uses same session ID as victim
Microservices & Distributed Systems
- Service-to-Service Authentication - Services authenticating each other (mTLS, service tokens)
- Federated Identity - Single sign-on across multiple systems
- Token Aggregation - Combining claims from multiple sources
- Permission Delegation - OAuth 2.0 delegation for API-to-API auth
Identity Providers & Platforms
- Auth0 - Third-party authentication-as-a-service (delegates auth to Auth0)
- Azure AD / Microsoft Entra - Enterprise identity management
- Okta - Identity and access management platform
- Firebase Authentication - Google's authentication backend
- IdentityServer - Open-source OAuth2/OpenID Connect server for ASP.NET Core
Testing & Validation
- JWT Debugger - jwt.io - Decode and inspect tokens
- Unit Testing Authentication - Mock token generation and validation
- Integration Testing - Test login flow end-to-end
- Security Testing - Penetration testing for auth vulnerabilities
Production Considerations
- Token Storage Strategies - localStorage vs cookies vs in-memory
- Token Rotation Strategies - When/how to issue new tokens
- Monitoring & Alerts - Detect unusual login patterns
- Compliance - GDPR, HIPAA, SOC2 auth requirements
- Performance Optimization - Cache token validation results