15. JWT with Auth0
- 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
- Create account at auth0.com
- Create new API in Auth0 dashboard
- Note these values:
- Domain: your-tenant.auth0.com
- API Identifier (Audience): https://your-api-identifier
- Create an application for testing (SPA or Machine-to-Machine)
- 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):
- appsettings.json
- appsettings.{Environment}.json
- User Secrets (Development only)
- Environment Variables
- 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"