15. JWT with Auth0

  1. JWT with Auth0

About this chapter

Integrate Auth0 for professional JWT-based authentication, learning to configure Auth0, validate tokens, and protect endpoints with authorization.

  • Auth0 account setup: Creating applications and APIs in Auth0 dashboard
  • JWT bearer configuration: Installing and configuring JwtBearer authentication scheme
  • Token validation: Checking issuer, audience, and signature
  • Endpoint protection: Using [Authorize] and [AllowAnonymous] attributes
  • Role-based authorization: Restricting access by user roles
  • Testing authenticated endpoints: Using tools to send requests with JWT tokens

Learning outcomes:

  • Set up Auth0 account and configure API/applications
  • Add JWT bearer authentication to ASP.NET Core
  • Validate tokens with Auth0 credentials
  • Protect controllers and endpoints with [Authorize]
  • Test authenticated endpoints with bearer tokens
  • Use Auth0 for production authentication

15.1 Setting Up Auth0 Account

  1. Create account at auth0.com
  2. Create new API in Auth0 dashboard
  3. Note these values:
  • Domain: your-tenant.auth0.com
  • API Identifier (Audience): https://your-api-identifier
  1. Create an application for testing (SPA or Machine-to-Machine)
  2. Get test tokens from Auth0 dashboard or using OAuth flow

15.2 Configuring JWT Bearer Authentication

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
// Program.cs
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    // Auth0 configuration
    options.Authority = builder.Configuration["Auth0:Authority"];
    // e.g., "https://dev-abc123.us.auth0.com"
    
    options.Audience = builder.Configuration["Auth0:ApiIdentifier"];
    // e.g., "https://localhost:7213"
    
    // Token validation parameters
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = builder.Configuration["Auth0:Authority"],
        
        ValidateAudience = true,
        ValidAudience = builder.Configuration["Auth0:ApiIdentifier"],
        
        ValidateLifetime = true,
        ClockSkew = TimeSpan.FromMinutes(5),  // Allow 5 min clock skew
        
        ValidateIssuerSigningKey = true,
        // Signing key fetched automatically from Authority/.well-known/jwks.json
    };
    
    // Event handlers for debugging/logging
    options.Events = new JwtBearerEvents
    {
        OnAuthenticationFailed = context =>
        {
            var logger = context.HttpContext.RequestServices
                .GetRequiredService<ILogger<Program>>();
            logger.LogError(context.Exception, "JWT authentication failed");
            return Task.CompletedTask;
        },
        
        OnTokenValidated = context =>
        {
            var logger = context.HttpContext.RequestServices
                .GetRequiredService<ILogger<Program>>();
            var userId = context.Principal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            logger.LogInformation("Token validated for user: {UserId}", userId);
            return Task.CompletedTask;
        }
    };
});

15.3 Protecting Endpoints with [Authorize]

// Protect entire controller
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class PlatformsController : ControllerBase
{
    // All actions require authentication
}

// Mix authenticated and anonymous
[Route("api/[controller]")]
[ApiController]
public class PlatformsController : ControllerBase
{
    [HttpGet]  // Anonymous allowed
    public async Task<ActionResult> GetAll() { }
    
    [Authorize]  // Requires authentication
    [HttpPost]
    public async Task<ActionResult> Create() { }
    
    [AllowAnonymous]  // Explicitly allow anonymous even if controller has [Authorize]
    [HttpGet("public")]
    public async Task<ActionResult> GetPublicData() { }
}

// Role-based authorization
[Authorize(Roles = "Admin")]
[HttpDelete("{id}")]
public async Task<ActionResult> Delete(int id) { }

// Policy-based authorization
[Authorize(Policy = "PremiumOnly")]
[HttpGet("premium-features")]
public async Task<ActionResult> GetPremiumFeatures() { }

15.4 Testing Authenticated Endpoints

@baseUrl = https://localhost:7213
@accessToken = eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

### Get platforms (anonymous)
GET {{baseUrl}}/api/platforms

### Create platform (requires JWT)
POST {{baseUrl}}/api/platforms
Content-Type: application/json
Authorization: Bearer {{accessToken}}

{
    "platformName": "Kubernetes"
}

### Test with expired token (should get 401)
POST {{baseUrl}}/api/platforms
Content-Type: application/json
Authorization: Bearer expired_token_here

{
    "platformName": "Test"
}

Getting Test Tokens:

# Using curl to get token from Auth0
curl --request POST \
  --url https://your-tenant.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{
    "client_id": "your_client_id",
    "client_secret": "your_client_secret",
    "audience": "https://your-api-identifier",
    "grant_type": "client_credentials"
  }'

15.5 CORRECTION: Using User Secrets, Not Hardcoded Config

# Initialize user secrets
dotnet user-secrets init

# Add secrets (never commit these!)
dotnet user-secrets set "Auth0:Authority" "https://dev-abc123.us.auth0.com"
dotnet user-secrets set "Auth0:ApiIdentifier" "https://localhost:7213"
dotnet user-secrets set "DbPassword" "your_secure_password"
// ❌ appsettings.Development.json (current code has this)
{
  "Auth0": {
    "Authority": "https://dev-b7dmqenm.us.auth0.com",  // Exposed!
    "ApiIdentifier": "https://localhost:7213"
  },
  "DbPassword": "pa55w0rd"  // NEVER do this!
}

// ✅ appsettings.Development.json (proper way)
{
  "Auth0": {
    "Authority": "",  // Set via user secrets or env vars
    "ApiIdentifier": ""
  }
}

Configuration Hierarchy (later wins):

  1. appsettings.json
  2. appsettings.{Environment}.json
  3. User Secrets (Development only)
  4. Environment Variables
  5. Command-line arguments

Production Configuration:

# Environment variables (Azure, AWS, etc.)
export Auth0__Authority="https://prod-tenant.auth0.com"
export Auth0__ApiIdentifier="https://api.production.com"
export DbPassword="secure_production_password"