Isaac.

ASP.NET Core JWT Authentication

Implement JWT-based authentication in ASP.NET Core.

By EMEPublished: March 11, 2025
aspnetjwtauthenticationsecurity

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):

  1. User logs in with username/password
  2. Server creates session, stores in database: {userId: 123, username: 'john'}
  3. Server sends session ID as cookie
  4. Client includes cookie in every request
  5. For every request, server queries database: "Does session ID 'xyz' exist?"

This gets slow with millions of requests.

The Solution With JWT

  1. User logs in once (verify password)
  2. Server creates JWT containing user info + signature
  3. Client stores JWT (in localStorage, secure storage, or cookies)
  4. Client includes JWT in every request header: Authorization: Bearer <token>
  5. Server verifies signature only (no database lookup needed!)
  6. 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:

  1. How to verify JWT signatures
  2. What claims to trust
  3. When tokens expire
  4. Who issued the token ("Issuer")
  5. 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 key
  • IssuerSigningKey - The secret used to sign/verify
  • ValidateIssuer - Check token came from YOUR server
  • ValidateAudience - Check token is FOR YOUR app
  • ValidateLifetime - Check token hasn't expired
  • ClockSkew = 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:

  1. User submits username/password
  2. Server verifies credentials against database
  3. 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
  4. 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:

  1. Server receives JWT
  2. Server extracts header and payload
  3. Server computes signature using same algorithm and secret key
  4. Server compares computed signature with received signature
  5. If they match: ✓ Token is valid (not tampered with)
  6. 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" to role: "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