14. Authentication & Authorization

  1. Authentication & Authorization

About this chapter

Secure your API by implementing authentication to verify user identity and authorization to control what authenticated users can access.

  • Authentication concepts: Verifying user identity with credentials
  • Authorization concepts: Controlling resource access based on permissions
  • JWT bearer tokens: Stateless token-based authentication
  • Token structure and claims: Understanding JWT composition
  • Claims-based identity: Using claims for fine-grained authorization
  • Implementing authentication: Configuring authentication schemes in .NET 10

Learning outcomes:

  • Distinguish between authentication and authorization
  • Understand JWT structure and benefits/drawbacks
  • Implement JWT authentication in ASP.NET Core
  • Work with claims and ClaimsPrincipal
  • Configure authentication middleware
  • Protect endpoints with authorization attributes

14.1 Understanding Authentication vs Authorization

┌─────────────────────────────────────────────────────────────┐
│  Authentication (AuthN): "Who are you?"                      │
│  ├─ Verifies identity                                        │
│  ├─ Credentials: username/password, token, certificate       │
│  └─ Result: ClaimsPrincipal with claims about the user       │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  Authorization (AuthZ): "What can you do?"                   │
│  ├─ Verifies permissions                                     │
│  ├─ Based on: roles, policies, claims                        │
│  └─ Result: Allow or deny access to resources                │
└─────────────────────────────────────────────────────────────┘

Real-World Analogy:

  • Authentication: Showing your ID at airport security
  • Authorization: Your ticket determines which gate/lounge you can access

14.2 JWT Bearer Tokens Overview

JWT Structure: header.payload.signature

Header (base64):
{
  "alg": "RS256",
  "typ": "JWT"
}

Payload (base64):
{
  "sub": "auth0|123456",
  "name": "John Doe",
  "email": "john@example.com",
  "iat": 1516239022,
  "exp": 1516242622,
  "aud": "https://api.example.com",
  "iss": "https://your-tenant.auth0.com/"
}

Signature:
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

JWT Benefits:

  • Stateless (no server-side session storage)
  • Self-contained (all info in token)
  • Cross-domain (CORS-friendly)
  • Scalable (no session sharing needed)

JWT Drawbacks:

  • Cannot revoke before expiration (unless using token blacklist)
  • Size larger than session IDs
  • Exposed data (base64 is encoding, not encryption)

14.3 Claims-Based Identity

// Claims are key-value pairs about the user
var claims = new[]
{
    new Claim(ClaimTypes.NameIdentifier, "auth0|123456"),
    new Claim(ClaimTypes.Name, "John Doe"),
    new Claim(ClaimTypes.Email, "john@example.com"),
    new Claim("subscription_level", "premium"),
    new Claim("created_date", "2023-01-15")
};

var identity = new ClaimsIdentity(claims, "Bearer");
var principal = new ClaimsPrincipal(identity);

// Accessing claims in controllers
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var email = User.FindFirst(ClaimTypes.Email)?.Value;
var isPremium = User.HasClaim("subscription_level", "premium");

Common Claim Types:

  • ClaimTypes.NameIdentifier - Unique user ID (sub in JWT)
  • ClaimTypes.Name - Display name
  • ClaimTypes.Email - Email address
  • ClaimTypes.Role - User roles
  • Custom claims for app-specific data

14.4 CORRECTION: Explicitly Adding UseAuthentication() and UseAuthorization()

// ❌ WRONG (current code has comment saying these aren't needed)
// confirm we don't need to explicitly add Authentication and Authorization
app.MapControllers();

// ✅ CORRECT - Always be explicit!
var app = builder.Build();

app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("JavascriptClient");

// CRITICAL: Add these explicitly in correct order
app.UseAuthentication();  // First: Authenticate the user
app.UseAuthorization();   // Then: Authorize based on policies

app.MapControllers();
app.Run();

Why Explicit is Better:

  • Clarity: Future developers understand the pipeline
  • Debugging: Easier to troubleshoot auth issues
  • Documentation: Self-documenting code
  • Framework Changes: Don’t rely on implicit behavior
  • Best Practice: Official Microsoft guidance