Skip to content

Latest commit

 

History

History
1391 lines (1053 loc) · 39.6 KB

File metadata and controls

1391 lines (1053 loc) · 39.6 KB

Auth0 ASP.NET Core API - Code Examples

This document provides practical, copy-pastable code examples for common scenarios when using the Auth0 ASP.NET Core API Authentication SDK.

Table of Contents

  1. Getting Started
  2. Configuration
  3. DPoP (Demonstration of Proof-of-Possession)
  4. Multiple Custom Domains
  5. Authorization
  6. Advanced Scenarios
  7. Integration Examples

Getting Started

1.1 Basic JWT Authentication

Basic setup for Auth0 JWT authentication in a minimal API.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

// Add Auth0 JWT authentication
builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/", () => "Hello, World!");

app.Run();

1.2 Protecting Minimal API Endpoints

Protect endpoints using RequireAuthorization() in minimal APIs.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// Public endpoint - no authentication required
app.MapGet("/api/public", () => Results.Ok(new { message = "This is a public endpoint" }));

// Protected endpoint - requires authentication
app.MapGet("/api/protected", () => Results.Ok(new { message = "You are authenticated!" }))
    .RequireAuthorization();

// Protected endpoint with user information
app.MapGet("/api/user", (HttpContext context) =>
{
    var userId = context.User.FindFirst("sub")?.Value;
    var email = context.User.FindFirst("email")?.Value;
    
    return Results.Ok(new { userId, email });
})
.RequireAuthorization();

app.Run();

1.3 Protecting Controller Endpoints

Protect endpoints using [Authorize] attribute in controllers.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
});

builder.Services.AddAuthorization();
builder.Services.AddControllers();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
    // Public endpoint
    [HttpGet("public")]
    public IActionResult GetPublic()
    {
        return Ok(new { message = "This is a public endpoint" });
    }

    // Protected endpoint
    [Authorize]
    [HttpGet("protected")]
    public IActionResult GetProtected()
    {
        var userId = User.FindFirst("sub")?.Value;
        return Ok(new { message = "You are authenticated!", userId });
    }

    // Protected POST endpoint
    [Authorize]
    [HttpPost]
    public IActionResult CreateData([FromBody] DataModel data)
    {
        return CreatedAtAction(nameof(GetProtected), new { id = data.Id }, data);
    }
}

public record DataModel(int Id, string Name);

Configuration

2.1 Custom Token Validation Parameters

Customize JWT token validation with specific parameters.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"],
        RequireHttpsMetadata = true,
        SaveToken = true,
        
        TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromMinutes(5),
            NameClaimType = "name",
            RoleClaimType = "https://schemas.auth0.com/roles"
        }
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/api/protected", () => Results.Ok(new { message = "Token validated with custom parameters" }))
    .RequireAuthorization();

app.Run();

DPoP (Demonstration of Proof-of-Possession)

DPoP is a security mechanism that binds access tokens to cryptographic keys, preventing token theft and replay attacks. This SDK provides seamless DPoP integration with flexible enforcement modes.

3.1 Enabling DPoP with Default Settings

Enable DPoP with a single method call - accepts both DPoP and Bearer tokens.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
}).WithDPoP(); // ✨ Enable DPoP with default settings (Allowed mode)

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// This endpoint works with both:
// 1. DPoP tokens (Authorization: DPoP <token> + DPoP: <proof>)
// 2. Bearer tokens (Authorization: Bearer <token>)
app.MapGet("/api/data", () => Results.Ok(new { message = "Supports both DPoP and Bearer tokens" }))
    .RequireAuthorization();

app.Run();

What this does:

  • Enables DPoP validation in Allowed mode (default)
  • Accepts DPoP-bound tokens with proof validation
  • Still accepts regular Bearer tokens for backward compatibility
  • Uses default time validation settings (300s iat offset, 30s leeway)

3.2 DPoP in Allowed Mode (Gradual Adoption)

Use Allowed mode to gradually adopt DPoP without breaking existing clients using Bearer tokens.

using Auth0.AspNetCore.Authentication.Api;
using Auth0.AspNetCore.Authentication.Api.DPoP;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
}).WithDPoP(dpopOptions =>
{
    // Explicitly set to Allowed mode (this is the default)
    dpopOptions.Mode = DPoPModes.Allowed;
    
    // Customize time validation
    dpopOptions.IatOffset = 300; // Allow DPoP proof tokens up to 5 minutes old
    dpopOptions.Leeway = 30;     // 30 seconds clock skew tolerance
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/api/legacy", () => 
    Results.Ok(new { message = "Works with Bearer tokens from legacy clients" }))
    .RequireAuthorization();

app.MapGet("/api/modern", () => 
    Results.Ok(new { message = "Works with DPoP tokens from modern clients" }))
    .RequireAuthorization();

app.Run();

Use this when:

  • You're migrating from Bearer tokens to DPoP
  • You have mixed clients (some support DPoP, some don't)
  • You want to test DPoP without forcing all clients to upgrade
  • You need a gradual rollout strategy

Security note: Allowed mode provides backward compatibility but doesn't enforce the full security benefits of DPoP for Bearer tokens.


3.3 DPoP in Required Mode (Strict Security)

Use Required mode when you want maximum security - only DPoP tokens are accepted.

using Auth0.AspNetCore.Authentication.Api;
using Auth0.AspNetCore.Authentication.Api.DPoP;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
}).WithDPoP(dpopOptions =>
{
    // Only accept DPoP tokens, reject Bearer tokens
    dpopOptions.Mode = DPoPModes.Required;
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// This endpoint ONLY accepts DPoP tokens
// Bearer tokens will be rejected with 401 Unauthorized
app.MapGet("/api/high-security", () => 
    Results.Ok(new { message = "DPoP token verified successfully" }))
    .RequireAuthorization();

app.Run();

Multiple Custom Domains

Multiple Custom Domains (MCD) lets you accept tokens from multiple Auth0 custom domains while keeping a single SDK instance. This is useful when one application serves multiple custom domains, each mapped to a different Auth0 custom domain.

MCD is intended for the custom domains of a single Auth0 tenant. It is not a supported way to connect multiple Auth0 tenants to one application.

4.1 Basic Custom Domains Configuration

Configure a static list of allowed Auth0 custom domains when they are known at application startup.

using Auth0.AspNetCore.Authentication.Api;
using Auth0.AspNetCore.Authentication.Api.CustomDomains;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
})
.WithCustomDomains(options =>
{
    // Static list of allowed Auth0 custom domains
    options.Domains = new[]
    {
        "brand-1.custom-domain.com",
        "brand-2.custom-domain.com",
        "brand-3.custom-domain.com"
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// This endpoint accepts tokens from any of the configured domains
app.MapGet("/api/data", () =>
    Results.Ok(new { message = "Authenticated from any allowed domain" }))
    .RequireAuthorization();

app.Run();

What this does:

  • Accepts JWT tokens issued by any domain in the Domains list
  • Automatically validates token issuer against the allowed list before any network calls
  • Rejects tokens from unauthorized domains with 401 Unauthorized
  • Uses in-memory cache for OIDC configurations (100 entries, 10-minute sliding expiration by default)

Use this when:

  • You have a fixed set of Auth0 custom domains known at application startup
  • You need simple, straightforward multi-domain configuration

4.2 Dynamic Domain Resolution

Resolve allowed domains dynamically at runtime based on request context, database lookups, or external APIs.

using Auth0.AspNetCore.Authentication.Api;
using Auth0.AspNetCore.Authentication.Api.CustomDomains;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

// Register a tenant service for domain resolution
builder.Services.AddSingleton<ITenantService, TenantService>();

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
})
.WithCustomDomains(options =>
{
    // Dynamic domain resolution using request context
    options.DomainsResolver = async (httpContext, cancellationToken) =>
    {
        // Example: resolve from a request header
        var tenantId = httpContext.Request.Headers["X-Tenant-Id"].FirstOrDefault();

        // Example: resolve from a database or service
        var tenantService = httpContext.RequestServices.GetRequiredService<ITenantService>();
        var domains = await tenantService.GetAllowedDomainsAsync(tenantId, cancellationToken);

        return domains;
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/api/tenant-data", () =>
    Results.Ok(new { message = "Domain resolved dynamically" }))
    .RequireAuthorization();

app.Run();

What this does:

  • Resolves allowed domains dynamically for each request
  • Provides full HttpContext access for custom resolution logic
  • Supports database queries, external API calls, or complex business logic

Use this when:

  • Domains are not known at startup (e.g., customer onboarding creates new tenants)
  • You need request-specific domain resolution (headers, query params, claims)
  • Tenant configuration is stored in a database or external service
  • Building a white-label SaaS with customer-specific Auth0 custom domains

Important: DomainsResolver and Domains are mutually exclusive — configure only one.


4.3 Custom Cache Configuration

You can control how OpenID Connect configuration managers are cached per domain with ConfigurationManagerCache.

By default, the SDK uses an in-memory cache with:

  • maxSize: 100 entries
  • No expiration (entries remain until evicted by size pressure)

The cache is keyed by the OIDC metadata endpoint URL (e.g., https://brand-1.custom-domain.com/.well-known/openid-configuration). Each distinct domain occupies one cache entry.

MemoryConfigurationManagerCache (Default)

using Auth0.AspNetCore.Authentication.Api;
using Auth0.AspNetCore.Authentication.Api.CustomDomains;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
})
.WithCustomDomains(options =>
{
    options.Domains = new[]
    {
        "brand-1.custom-domain.com",
        "brand-2.custom-domain.com"
    };

    // Customize cache settings
    options.ConfigurationManagerCache = new MemoryConfigurationManagerCache(
        maxSize: 50,                              // Maximum number of cached configurations
        slidingExpiration: TimeSpan.FromHours(1)  // Evict entries not accessed within 1 hour
    );

    // Customize OIDC refresh intervals
    options.AutomaticRefreshInterval = TimeSpan.FromHours(24);
    options.RefreshInterval = TimeSpan.FromMinutes(10);
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/api/data", () =>
    Results.Ok(new { message = "Using custom cache configuration" }))
    .RequireAuthorization();

app.Run();

NullConfigurationManagerCache

Disables caching entirely — a new configuration manager is created on every request (not recommended for production):

.WithCustomDomains(options =>
{
    options.Domains = new[] { "brand-1.custom-domain.com" };

    // Disable caching entirely (fetch OIDC config on every request)
    options.ConfigurationManagerCache = new NullConfigurationManagerCache();
});

Custom Cache Implementation

Implement IConfigurationManagerCache for custom caching strategies (e.g., a distributed cache):

using Auth0.AspNetCore.Authentication.Api.CustomDomains;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

public class MyCustomConfigurationManagerCache : IConfigurationManagerCache
{
    public IConfigurationManager<OpenIdConnectConfiguration> GetOrCreate(
        string metadataAddress,
        Func<string, IConfigurationManager<OpenIdConnectConfiguration>> factory)
    {
        // Return a cached instance or call factory(metadataAddress) to create one
        throw new NotImplementedException();
    }

    public void Clear() { /* Evict all entries */ }
    public void Dispose() { /* Clean up resources */ }
}

// Usage
.WithCustomDomains(options =>
{
    options.Domains = new[] { "brand-1.custom-domain.com" };
    options.ConfigurationManagerCache = new MyCustomConfigurationManagerCache();
});

Security requirements

When configuring the DomainsResolver, you are responsible for ensuring that all resolved domains are trusted. Mis-configuring the domain resolver is a critical security risk that can lead to authentication bypass on the relying party (RP) or expose the application to Server-Side Request Forgery (SSRF).

Single tenant limitation: The DomainsResolver is intended solely for multiple custom domains belonging to the same Auth0 tenant. It is not a supported mechanism for connecting multiple Auth0 tenants to a single application.

Secure proxy requirement: When using MCD, your application must be deployed behind a secure edge or reverse proxy (e.g., Cloudflare, Nginx, or AWS ALB). The proxy must be configured to sanitize and overwrite Host and X-Forwarded-Host headers before they reach your application.

Without a trusted proxy layer to validate these headers, an attacker can manipulate the domain resolution process. This can result in authentication bypass or exposure to Server-Side Request Forgery (SSRF).


Authorization

5.1 Scope-Based Authorization with Policies

Validate scopes from Auth0 access tokens using authorization policies.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
});

// Define scope-based authorization policies
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("read:data", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "scope" &&
                c.Value.Split(' ').Contains("read:data"))));
    
    options.AddPolicy("write:data", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "scope" &&
                c.Value.Split(' ').Contains("write:data"))));
    
    options.AddPolicy("delete:data", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "scope" &&
                c.Value.Split(' ').Contains("delete:data"))));
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    // Requires 'read:data' scope
    [Authorize(Policy = "read:data")]
    [HttpGet]
    public IActionResult GetProducts()
    {
        var products = new[] { "Product 1", "Product 2", "Product 3" };
        return Ok(products);
    }

    // Requires 'write:data' scope
    [Authorize(Policy = "write:data")]
    [HttpPost]
    public IActionResult CreateProduct([FromBody] ProductModel product)
    {
        return CreatedAtAction(nameof(GetProducts), new { id = product.Id }, product);
    }

    // Requires 'delete:data' scope
    [Authorize(Policy = "delete:data")]
    [HttpDelete("{id}")]
    public IActionResult DeleteProduct(int id)
    {
        return NoContent();
    }
}

public record ProductModel(int Id, string Name);

5.2 Permission-Based Authorization

Validate Auth0 permissions using custom authorization policies.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
});

// Define permission-based authorization policies
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CanReadUsers", policy =>
        policy.RequireClaim("permissions", "read:users"));
    
    options.AddPolicy("CanCreateUsers", policy =>
        policy.RequireClaim("permissions", "create:users"));
    
    options.AddPolicy("CanDeleteUsers", policy =>
        policy.RequireClaim("permissions", "delete:users"));
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [Authorize(Policy = "CanReadUsers")]
    [HttpGet]
    public IActionResult GetUsers()
    {
        var users = new[] { "User 1", "User 2", "User 3" };
        return Ok(users);
    }

    [Authorize(Policy = "CanCreateUsers")]
    [HttpPost]
    public IActionResult CreateUser([FromBody] UserModel user)
    {
        return CreatedAtAction(nameof(GetUsers), new { id = user.Id }, user);
    }

    [Authorize(Policy = "CanDeleteUsers")]
    [HttpDelete("{id}")]
    public IActionResult DeleteUser(string id)
    {
        return NoContent();
    }
}

public record UserModel(string Id, string Name, string Email);

5.3 Custom Authorization Handler

Create a reusable authorization handler for scope validation.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
});

// Register custom authorization handler
builder.Services.AddSingleton<IAuthorizationHandler, HasScopeHandler>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("read:products", policy =>
        policy.Requirements.Add(new HasScopeRequirement("read:products")));
    
    options.AddPolicy("write:products", policy =>
        policy.Requirements.Add(new HasScopeRequirement("write:products")));
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

// Custom authorization requirement
public class HasScopeRequirement : IAuthorizationRequirement
{
    public string Scope { get; }

    public HasScopeRequirement(string scope)
    {
        Scope = scope ?? throw new ArgumentNullException(nameof(scope));
    }
}

// Custom authorization handler
public class HasScopeHandler : AuthorizationHandler<HasScopeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        HasScopeRequirement requirement)
    {
        var scopeClaim = context.User.FindFirst(c => c.Type == "scope");

        if (scopeClaim != null)
        {
            var scopes = scopeClaim.Value.Split(' ');
            if (scopes.Contains(requirement.Scope))
            {
                context.Succeed(requirement);
            }
        }

        return Task.CompletedTask;
    }
}

[ApiController]
[Route("api/[controller]")]
public class InventoryController : ControllerBase
{
    [Authorize(Policy = "read:products")]
    [HttpGet]
    public IActionResult GetInventory()
    {
        return Ok(new[] { "Item 1", "Item 2" });
    }

    [Authorize(Policy = "write:products")]
    [HttpPost]
    public IActionResult AddInventory([FromBody] InventoryItem item)
    {
        return Created($"/api/inventory/{item.Id}", item);
    }
}

public record InventoryItem(int Id, string Name, int Quantity);

5.4 Role-Based Authorization

Validate Auth0 roles using authorization policies.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"],
        TokenValidationParameters = new TokenValidationParameters
        {
            RoleClaimType = "https://schemas.auth0.com/roles"
        }
    };
});

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
    options.AddPolicy("ManagerOrAdmin", policy => policy.RequireRole("Manager", "Admin"));
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

[ApiController]
[Route("api/[controller]")]
public class AdminController : ControllerBase
{
    [Authorize(Policy = "AdminOnly")]
    [HttpGet("settings")]
    public IActionResult GetSettings()
    {
        return Ok(new { setting = "Admin settings" });
    }

    [Authorize(Policy = "ManagerOrAdmin")]
    [HttpGet("reports")]
    public IActionResult GetReports()
    {
        var userRoles = User.FindAll("https://schemas.auth0.com/roles")
            .Select(c => c.Value);
        
        return Ok(new { message = "Reports data", roles = userRoles });
    }
}

Advanced Scenarios

6.1 Accessing User Claims

Access and use user claims from Auth0 tokens.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"]
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// Access user claims in minimal API
app.MapGet("/api/profile", (HttpContext context) =>
{
    var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
        ?? context.User.FindFirst("sub")?.Value;
    return Results.Ok(new { userId });
})
.RequireAuthorization();

// Access all claims
app.MapGet("/api/claims", (ClaimsPrincipal user) =>
{
    var claims = user.Claims.Select(c => new { c.Type, c.Value });
    return Results.Ok(claims);
})
.RequireAuthorization();

app.Run();

6.2 Custom JWT Bearer Events

Implement custom logic during JWT authentication events.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"],
        Events = new JwtBearerEvents
        {
            OnTokenValidated = context =>
            {
                var logger = context.HttpContext.RequestServices
                    .GetRequiredService<ILogger<Program>>();
                
                var userId = context.Principal?.FindFirst("sub")?.Value;
                logger.LogInformation("Token validated for user: {UserId}", userId);
                
                // Add custom claims
                var identity = context.Principal?.Identity as ClaimsIdentity;
                identity?.AddClaim(new Claim("validated_at", DateTime.UtcNow.ToString()));
                
                return Task.CompletedTask;
            },
            
            OnAuthenticationFailed = context =>
            {
                var logger = context.HttpContext.RequestServices
                    .GetRequiredService<ILogger<Program>>();
                
                logger.LogError(context.Exception, "Authentication failed");
                
                if (context.Exception is SecurityTokenExpiredException)
                {
                    context.Response.Headers.Append("Token-Expired", "true");
                }
                
                return Task.CompletedTask;
            },
            
            OnMessageReceived = context =>
            {
                var logger = context.HttpContext.RequestServices
                    .GetRequiredService<ILogger<Program>>();
                
                var hasToken = !string.IsNullOrEmpty(context.Token);
                logger.LogDebug("Token received: {HasToken}", hasToken);
                
                return Task.CompletedTask;
            },
            
            OnChallenge = context =>
            {
                var logger = context.HttpContext.RequestServices
                    .GetRequiredService<ILogger<Program>>();
                
                logger.LogWarning("Authentication challenge issued: {Error}", context.Error);
                
                return Task.CompletedTask;
            }
        }
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/api/protected", (ClaimsPrincipal user) =>
{
    var validatedAt = user.FindFirst("validated_at")?.Value;
    return Results.Ok(new { message = "Authenticated", validatedAt });
})
.RequireAuthorization();

app.Run();

6.3 Token Extraction from Query String

Extract JWT tokens from query string for scenarios like SignalR.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"],
        Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // Check for token in query string
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                
                // Allow token from query string for specific paths (e.g., SignalR hubs)
                if (!string.IsNullOrEmpty(accessToken) && 
                    (path.StartsWithSegments("/hubs") || path.StartsWithSegments("/api/stream")))
                {
                    context.Token = accessToken;
                }
                
                return Task.CompletedTask;
            }
        }
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// Standard endpoint - expects token in Authorization header
app.MapGet("/api/data", () => Results.Ok(new { data = "Standard endpoint" }))
    .RequireAuthorization();

// Streaming endpoint - can accept token from query string
app.MapGet("/api/stream", (HttpContext context) =>
{
    var userId = context.User.FindFirst("sub")?.Value;
    return Results.Ok(new { message = "Streaming data", userId });
})
.RequireAuthorization();

app.Run();

6.4 Custom Error Responses

Customize error responses for authentication failures.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"],
        Events = new JwtBearerEvents
        {
            OnChallenge = context =>
            {
                // Skip the default behavior
                context.HandleResponse();
                
                // Create custom error response
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                context.Response.ContentType = "application/json";
                
                var errorResponse = new
                {
                    error = "unauthorized",
                    message = "Authentication is required to access this resource",
                    timestamp = DateTime.UtcNow
                };
                
                return context.Response.WriteAsync(
                    JsonSerializer.Serialize(errorResponse));
            },
            
            OnAuthenticationFailed = context =>
            {
                if (context.Exception != null)
                {
                    var logger = context.HttpContext.RequestServices
                        .GetRequiredService<ILogger<Program>>();
                    
                    logger.LogError(context.Exception, 
                        "Authentication failed: {Message}", context.Exception.Message);
                }
                
                return Task.CompletedTask;
            }
        }
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/api/protected", () => Results.Ok(new { data = "Protected resource" }))
    .RequireAuthorization();

app.Run();

Integration Examples

7.1 SignalR Integration

Integrate Auth0 authentication with SignalR hubs.

using Auth0.AspNetCore.Authentication.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuth0ApiAuthentication(options =>
{
    options.Domain = builder.Configuration["Auth0:Domain"];
    options.JwtBearerOptions = new JwtBearerOptions
    {
        Audience = builder.Configuration["Auth0:Audience"],
        Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                // Extract token from query string for SignalR
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
                
                if (!string.IsNullOrEmpty(accessToken) &&
                    path.StartsWithSegments("/hubs"))
                {
                    context.Token = accessToken;
                }
                
                return Task.CompletedTask;
            }
        }
    };
});

builder.Services.AddAuthorization();
builder.Services.AddSignalR();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapHub<ChatHub>("/hubs/chat");

app.Run();

// SignalR Hub with authentication
[Authorize]
public class ChatHub : Hub
{
    private readonly ILogger<ChatHub> _logger;

    public ChatHub(ILogger<ChatHub> logger)
    {
        _logger = logger;
    }

    public override async Task OnConnectedAsync()
    {
        var userId = Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value
            ?? Context.User?.FindFirst("sub")?.Value;
        var connectionId = Context.ConnectionId;
        
        _logger.LogInformation("User {UserId} connected with ID {ConnectionId}", 
            userId, connectionId);
        
        await Clients.All.SendAsync("UserConnected", userId);
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        var userId = Context.User?.FindFirst("sub")?.Value;
        
        _logger.LogInformation("User {UserId} disconnected", userId);
        
        await Clients.All.SendAsync("UserDisconnected", userId);
        await base.OnDisconnectedAsync(exception);
    }

    public async Task SendMessage(string message)
    {
        var userId = Context.User?.FindFirst("sub")?.Value;
        var userName = Context.User?.FindFirst("name")?.Value ?? "Anonymous";
        
        await Clients.All.SendAsync("ReceiveMessage", userName, message);
    }

    // Only users with specific scope can broadcast
    [Authorize(Policy = "write:messages")]
    public async Task BroadcastMessage(string message)
    {
        var userName = Context.User?.FindFirst("name")?.Value ?? "System";
        await Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

Getting an Auth0 Access Token

To test these examples, you'll need an access token from Auth0. Here's how to get one:

Using cURL

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"
  }'

Making Authenticated Requests

# Replace YOUR_ACCESS_TOKEN with the token from above
curl --request GET \
  --url https://localhost:5000/api/protected \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Additional Resources


Support

If you have questions or need help with these examples: