← Files Auth0ARCHIVED FILE
skills/auth0/references/framework-aspnetcore-api/index.md
25.2 KB · Oct 5, 2026 · 18:33 UTC
# Auth0 ASP.NET Core Web API Integration
Protect ASP.NET Core Web API endpoints with JWT access token validation using Auth0.AspNetCore.Authentication.Api.
## Prerequisites
- .NET 8.0 SDK or higher
- Auth0 API configured (not Application - must be API resource)
- If Auth0 isn't set up yet, set it up first with the Auth0 CLI (`auth0 login`, then `auth0 apps create`)
## When NOT to Use
- **Server-rendered web applications** - Use session-based auth (Auth0.AspNetCore.Authentication) for MVC/Razor Pages apps
- **Single Page Applications** - Use the Auth0 React, Vue, or Angular integration workflow for client-side auth
- **Mobile applications** - Use the Auth0 React Native integration workflow for React Native/Expo
- **Blazor WebAssembly** - Requires different auth approach (OIDC client-side)
## Quick Start Workflow
### 1. Install SDK
```bash
dotnet add package Auth0.AspNetCore.Authentication.Api
```
### 2. Create Auth0 API
You need an **API** (not Application) in Auth0.
> **STOP — ask the user before proceeding.**
>
> Ask exactly this question and wait for their answer before doing anything else:
>
> > "How would you like to create the Auth0 API resource?
> > 1. **Automated** — I'll run Auth0 CLI scripts that create the resource and write the exact values to your appsettings.json automatically.
> > 2. **Manual** — You create the API yourself in the Auth0 Dashboard (or via `auth0 apis create`) and provide me the Domain and Audience.
> >
> > Which do you prefer? (1 = Automated / 2 = Manual)"
>
> Do NOT proceed to any setup steps until the user has answered. Do NOT default to manual.
**If the user chose Automated**, follow the Setup Guide section below for complete CLI scripts. The automated path writes `appsettings.json` for you — skip Step 3 below and proceed directly to Step 4.
**If the user chose Manual**, follow the Setup Guide section below (Manual Setup) for full instructions including User Secrets and environment variable options. Then continue with Step 3 below.
Quick reference for manual API creation:
```bash
# Using Auth0 CLI
auth0 apis create \
--name "My ASP.NET Core API" \
--identifier https://my-api.example.com
```
Or create manually in Auth0 Dashboard → Applications → APIs
### 3. Configure appsettings.json
```json
{
"Auth0": {
"Domain": "your-tenant.auth0.com",
"Audience": "https://my-api.example.com"
}
}
```
**Important:** Domain must NOT include `https://`. The library constructs the authority URL automatically.
### 4. Configure Program.cs
```csharp
var builder = WebApplication.CreateBuilder(args);
// Register Auth0 JWT validation
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();
// Middleware order matters: authentication before authorization
app.UseAuthentication();
app.UseAuthorization();
// Add your endpoints here (see Step 5)
app.MapGet("/api/public", () => Results.Ok(new { message = "Public" }));
app.Run();
```
### 5. Protect Endpoints
**Minimal API:**
```csharp
// Public endpoint - no authentication
app.MapGet("/api/public", () => Results.Ok(new { message = "Hello from a public endpoint!" }));
// Protected endpoint - requires valid JWT
app.MapGet("/api/private", (HttpContext ctx) =>
{
var userId = ctx.User.FindFirst("sub")?.Value;
return Results.Ok(new { message = "Hello from a protected endpoint!", userId });
}).RequireAuthorization();
```
**Controller-based:**
```csharp
[ApiController]
[Route("api")]
public class MessagesController : ControllerBase
{
[HttpGet("public")]
public IActionResult Public() =>
Ok(new { message = "Hello from a public endpoint!" });
[Authorize]
[HttpGet("private")]
public IActionResult Private() =>
Ok(new { message = "Hello from a protected endpoint!", userId = User.FindFirst("sub")?.Value });
}
```
### 6. Test API
Test public endpoint:
```bash
curl http://localhost:5000/api/public
```
Test protected endpoint (requires access token):
```bash
curl http://localhost:5000/api/private \
-H "Authorization: Bearer $TOKEN"
```
Get a test token via Client Credentials flow or Auth0 Dashboard → APIs → Test tab.
Capture the token into a shell variable (`TOKEN=$(...)`) and reference `$TOKEN`
rather than pasting the raw token inline — inline token values leak into shell
history and terminal scrollback.
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Domain includes `https://` | Use `your-tenant.auth0.com` format only - no scheme prefix |
| Audience doesn't match API Identifier | Must exactly match the API Identifier set in Auth0 Dashboard |
| Created Application instead of API in Auth0 | Must create API resource in Auth0 Dashboard → Applications → APIs |
| Wrong middleware order | `UseAuthentication()` must come before `UseAuthorization()` |
| Using ID token instead of access token | Must use **access token** for API auth, not ID token |
| HTTPS certificate errors locally | Run `dotnet dev-certs https --trust` |
## Scope-Based Authorization
See the Scope-Based Authorization section below for defining and enforcing scope policies.
## DPoP Support
Built-in proof-of-possession token binding per RFC 9449. See the DPoP Support section below for configuration.
## Related Capabilities
- Basic Auth0 setup → set up Auth0 with the Auth0 CLI (`auth0 login`, then `auth0 apps create`)
- Multi-factor authentication → ask for MFA (feature:mfa)
- Manage Auth0 resources from the terminal → the Auth0 CLI (`tooling-cli`)
## Quick Reference
**Configuration Options:**
- `options.Domain` - Auth0 tenant domain, no `https://` prefix (required)
- `options.JwtBearerOptions.Audience` - API Identifier from Auth0 API settings (required)
- `options.JwtBearerOptions` - Full access to underlying Microsoft JWT Bearer options
**User Claims:**
- `ctx.User.FindFirst("sub")?.Value` - User ID (subject)
- `ctx.User.FindFirst("scope")?.Value` - Space-separated scopes
- `ctx.User.FindAll("scope")` - All scope claims
**Common Use Cases:**
- Protect Minimal API routes → `.RequireAuthorization()` (see Step 5)
- Protect controller actions → `[Authorize]` attribute (see Step 5)
- Scope enforcement → see the Scope-Based Authorization section below
- DPoP token binding → see the DPoP Support section below
- Advanced JWT Bearer config → see the API Reference section below
## References
- [Auth0 ASP.NET Core Web API Quickstart](https://auth0.com/docs/quickstart/backend/aspnet-core-webapi)
- [SDK GitHub Repository](https://github.com/auth0/aspnetcore-api)
- [API Documentation](https://auth0.github.io/aspnetcore-api)
- [Access Tokens Guide](https://auth0.com/docs/secure/tokens/access-tokens)
---
# Auth0 ASP.NET Core Web API - API Reference
Complete reference for Auth0.AspNetCore.Authentication.Api configuration options and extension methods.
---
## Extension Methods
### `AddAuth0ApiAuthentication`
Registers Auth0 JWT Bearer authentication with the dependency injection container.
```csharp
builder.Services.AddAuth0ApiAuthentication(options =>
{
options.Domain = "your-tenant.auth0.com";
options.JwtBearerOptions = new JwtBearerOptions
{
Audience = "https://my-api.example.com"
};
});
```
---
## Auth0ApiAuthenticationOptions
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `Domain` | `string` | Yes | Auth0 tenant domain. Format: `your-tenant.auth0.com` (no `https://` prefix) |
| `JwtBearerOptions` | `JwtBearerOptions` | Yes | Microsoft JWT Bearer options. Set `Audience` here. |
### `Domain`
Your Auth0 tenant domain. The library constructs the authority URL as `https://{Domain}/`.
```csharp
options.Domain = builder.Configuration["Auth0:Domain"];
// e.g., "dev-abc123.us.auth0.com"
```
### `JwtBearerOptions`
Full access to the underlying [Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbeareroptions).
Key sub-properties:
| Property | Type | Description |
|----------|------|-------------|
| `Audience` | `string` | API Identifier from Auth0. Must exactly match. |
| `TokenValidationParameters` | `TokenValidationParameters` | Additional token validation rules |
| `Events` | `JwtBearerEvents` | Hooks into authentication lifecycle |
| `SaveToken` | `bool` | Whether to save the raw token in the auth properties |
| `RequireHttpsMetadata` | `bool` | Defaults to `true` in production |
| `IncludeErrorDetails` | `bool` | Include error details in 401/403 responses |
---
## Auth0ApiAuthenticationBuilder
Returned by `AddAuth0ApiAuthentication`. Fluent builder for additional configuration.
### `.WithDPoP()`
Enables DPoP token validation with default settings (Allowed mode).
```csharp
builder.Services.AddAuth0ApiAuthentication(options => { ... })
.WithDPoP();
```
### `.WithDPoP(Action<DPoPOptions> configureDPoP)`
Enables DPoP with custom configuration.
```csharp
builder.Services.AddAuth0ApiAuthentication(options => { ... })
.WithDPoP(dpop =>
{
dpop.Mode = DPoPModes.Required;
dpop.IatOffset = 300;
dpop.Leeway = 30;
});
```
---
## DPoPOptions
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `Mode` | `DPoPModes` | `Allowed` | Controls which token types are accepted |
| `IatOffset` | `int` | `0` | Allowed clock skew in seconds for the `iat` claim |
| `Leeway` | `int` | `0` | Additional leeway in seconds for token time validation |
### DPoPModes Enum
| Value | Description |
|-------|-------------|
| `DPoPModes.Allowed` | Accept both DPoP-bound and standard Bearer tokens |
| `DPoPModes.Required` | Only accept DPoP-bound tokens; reject standard Bearer |
| `DPoPModes.Disabled` | Disable DPoP; standard JWT Bearer only |
---
## ASP.NET Core Authorization
Auth0 does not provide custom authorization attributes. Use standard ASP.NET Core authorization:
### Policy-Based Authorization
```csharp
// Register policies
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("read:messages", policy =>
policy.RequireClaim("scope", "read:messages"));
});
// Apply to Minimal API
app.MapGet("/endpoint", handler).RequireAuthorization("read:messages");
// Apply to controller action
[Authorize(Policy = "read:messages")]
public IActionResult GetMessages() { ... }
```
### Attribute-Based Authorization
```csharp
// Require any authenticated user
[Authorize]
public IActionResult Private() { ... }
// Require specific policy
[Authorize(Policy = "read:messages")]
public IActionResult Messages() { ... }
// Allow anonymous on an otherwise protected controller
[AllowAnonymous]
public IActionResult Public() { ... }
```
---
## JwtBearerEvents Hooks
Configure callbacks for authentication lifecycle events:
| Event | When | Common Use |
|-------|------|------------|
| `OnTokenValidated` | After token is validated | Extract custom claims, enrich identity |
| `OnAuthenticationFailed` | Token validation fails | Custom logging, error responses |
| `OnChallenge` | 401 response about to be sent | Customize 401 response body |
| `OnForbidden` | 403 response about to be sent | Customize 403 response body |
| `OnMessageReceived` | Before token extraction | Extract token from non-standard location |
**Example - Custom 401 response:**
```csharp
options.JwtBearerOptions = new JwtBearerOptions
{
Audience = "...",
Events = new JwtBearerEvents
{
OnChallenge = context =>
{
context.HandleResponse();
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
return context.Response.WriteAsJsonAsync(new
{
error = "unauthorized",
error_description = "A valid access token is required."
});
}
}
};
```
---
## References
- [Auth0 ASP.NET Core Web API Quickstart](https://auth0.com/docs/quickstart/backend/aspnet-core-webapi)
- [SDK GitHub Repository](https://github.com/auth0/aspnetcore-api)
- [Microsoft JWT Bearer Documentation](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/jwtbearer)
---
# Auth0 ASP.NET Core Web API Integration Patterns
Advanced integration patterns for ASP.NET Core Web API applications.
---
## Scope-Based Authorization
### Define Authorization Policies
In `Program.cs`, add policies that map to Auth0 API permissions:
```csharp
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("read:messages", policy =>
policy.RequireClaim("scope", "read:messages"));
options.AddPolicy("write:messages", policy =>
policy.RequireClaim("scope", "write:messages"));
options.AddPolicy("manage:orders", policy =>
{
policy.RequireClaim("scope", "read:orders");
policy.RequireClaim("scope", "write:orders");
});
});
```
### Apply Policies to Endpoints
**Minimal API:**
```csharp
app.MapGet("/api/messages", (HttpContext ctx) =>
{
return Results.Ok(new { messages = new[] { "Hello", "World" } });
}).RequireAuthorization("read:messages");
app.MapPost("/api/messages", (HttpContext ctx) =>
{
return Results.Created("/api/messages/1", new { id = 1 });
}).RequireAuthorization("write:messages");
```
**Controller-based:**
```csharp
[ApiController]
[Route("api/messages")]
public class MessagesController : ControllerBase
{
[HttpGet]
[Authorize(Policy = "read:messages")]
public IActionResult GetMessages() =>
Ok(new { messages = new[] { "Hello", "World" } });
[HttpPost]
[Authorize(Policy = "write:messages")]
public IActionResult CreateMessage() =>
Created("/api/messages/1", new { id = 1 });
}
```
### Define Permissions in Auth0
1. Go to Auth0 Dashboard → Applications → APIs
2. Select your API
3. Click the **Permissions** tab
4. Add permissions matching your policy names (e.g., `read:messages`, `write:messages`)
### Request Tokens with Scopes
Clients must request tokens that include the required scopes:
```bash
# Client Credentials with specific scopes
curl -X POST https://your-tenant.auth0.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"audience": "https://my-api.example.com",
"grant_type": "client_credentials",
"scope": "read:messages write:messages"
}'
```
---
## DPoP Support
DPoP (Demonstrating Proof of Possession, RFC 9449) binds tokens to a specific client key pair, preventing token theft.
### Enable DPoP
```csharp
builder.Services.AddAuth0ApiAuthentication(options =>
{
options.Domain = builder.Configuration["Auth0:Domain"];
options.JwtBearerOptions = new JwtBearerOptions
{
Audience = builder.Configuration["Auth0:Audience"]
};
})
.WithDPoP(); // Accept both DPoP and Bearer tokens (Allowed mode)
```
### DPoP Required Mode
To reject standard Bearer tokens and accept only DPoP-bound tokens:
```csharp
.WithDPoP(dpopOptions =>
{
dpopOptions.Mode = DPoPModes.Required;
});
```
Optionally configure clock skew tolerance:
```csharp
.WithDPoP(dpopOptions =>
{
dpopOptions.Mode = DPoPModes.Required;
dpopOptions.IatOffset = 300; // Allow 5-minute clock skew for iat claim
dpopOptions.Leeway = 30; // 30-second leeway for token validation
});
```
### DPoP Modes
| Mode | Behavior |
|------|----------|
| `DPoPModes.Allowed` (default) | Accept both DPoP-bound and standard Bearer tokens |
| `DPoPModes.Required` | Only accept DPoP-bound tokens; reject standard Bearer |
| `DPoPModes.Disabled` | Standard JWT Bearer only, DPoP disabled |
### Enable DPoP on Auth0 API
1. Go to Auth0 Dashboard → Applications → APIs
2. Select your API
3. Enable **Allow Skipping User Consent** and enable DPoP binding requirement
---
## Accessing User Claims
### From HttpContext in Minimal API
```csharp
app.MapGet("/api/profile", (HttpContext ctx) =>
{
var userId = ctx.User.FindFirst("sub")?.Value;
var email = ctx.User.FindFirst("https://example.com/email")?.Value; // custom claim
var scopes = ctx.User.FindFirst("scope")?.Value?.Split(' ') ?? [];
return Results.Ok(new { userId, scopes });
}).RequireAuthorization();
```
### From Controller
```csharp
[Authorize]
[HttpGet("profile")]
public IActionResult GetProfile()
{
var userId = User.FindFirst("sub")?.Value;
var scopes = User.FindFirst("scope")?.Value?.Split(' ') ?? [];
return Ok(new { userId, scopes });
}
```
### Common JWT Claims
| Claim | Description |
|-------|-------------|
| `sub` | User ID (subject) |
| `scope` | Space-separated list of granted scopes |
| `aud` | Audience (your API identifier) |
| `iss` | Issuer (your Auth0 tenant URL) |
| `exp` | Expiration timestamp |
| `iat` | Issued-at timestamp |
Custom claims added via Auth0 Actions use namespaced keys, e.g., `https://example.com/role`.
---
## Error Handling
### Return Problem Details for Auth Errors
```csharp
builder.Services.AddProblemDetails();
// Customize auth error responses
builder.Services.AddAuth0ApiAuthentication(options =>
{
options.Domain = builder.Configuration["Auth0:Domain"];
options.JwtBearerOptions = new JwtBearerOptions
{
Audience = builder.Configuration["Auth0:Audience"],
Events = new JwtBearerEvents
{
OnChallenge = context =>
{
context.HandleResponse();
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
return context.Response.WriteAsJsonAsync(new
{
error = "unauthorized",
error_description = "A valid access token is required."
});
},
OnForbidden = context =>
{
context.Response.StatusCode = 403;
context.Response.ContentType = "application/json";
return context.Response.WriteAsJsonAsync(new
{
error = "insufficient_scope",
error_description = "The access token does not have the required scopes."
});
}
}
};
});
```
### Standard Error Responses
| Status | Cause | Fix |
|--------|-------|-----|
| 401 | Missing or invalid token | Include valid `Authorization: Bearer <token>` header |
| 401 | Expired token | Request a fresh access token |
| 401 | Wrong audience | Token's `aud` claim must match your API Identifier |
| 403 | Insufficient scope | Token must include required scopes |
---
## Mixed Public and Protected Endpoints
```csharp
// Public - no auth needed
app.MapGet("/api/public", () =>
Results.Ok(new { message = "Public endpoint" }));
// Protected - requires valid JWT
app.MapGet("/api/private", (HttpContext ctx) =>
Results.Ok(new { message = "Private endpoint", userId = ctx.User.FindFirst("sub")?.Value }))
.RequireAuthorization();
// Protected with scope
app.MapGet("/api/messages", (HttpContext ctx) =>
Results.Ok(new { messages = Array.Empty<string>() }))
.RequireAuthorization("read:messages");
```
---
## Custom Token Validation
For advanced scenarios, configure additional JWT validation parameters:
```csharp
builder.Services.AddAuth0ApiAuthentication(options =>
{
options.Domain = builder.Configuration["Auth0:Domain"];
options.JwtBearerOptions = new JwtBearerOptions
{
Audience = builder.Configuration["Auth0:Audience"],
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "sub", // Map sub claim to User.Identity.Name
ClockSkew = TimeSpan.FromSeconds(30)
}
};
});
```
---
## Testing
### Integration Testing with WebApplicationFactory
```csharp
public class ApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public ApiTests(WebApplicationFactory<Program> factory) =>
_factory = factory;
[Fact]
public async Task PublicEndpoint_Returns200()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/api/public");
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task ProtectedEndpoint_WithoutToken_Returns401()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/api/private");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task ProtectedEndpoint_WithValidToken_Returns200()
{
// Option 1: Real token from Auth0 CLI (requires network, good for integration tests)
// auth0 test token --audience https://my-api.example.com
//
// Option 2: Mock JWT for fast unit tests — override auth in WebApplicationFactory:
// _factory.WithWebHostBuilder(b => b.ConfigureTestServices(services =>
// {
// services.PostConfigure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, o =>
// {
// o.TokenValidationParameters = new TokenValidationParameters
// {
// ValidateIssuer = false,
// ValidateAudience = false,
// ValidateLifetime = false,
// SignatureValidator = (token, _) => new JwtSecurityToken(token)
// };
// });
// }));
// Then generate a token with: new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(...))
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "YOUR_TEST_TOKEN");
var response = await client.GetAsync("/api/private");
response.EnsureSuccessStatusCode();
}
}
```
---
## Security Considerations
- **Never hardcode Domain or Audience** - Always use configuration (appsettings, User Secrets, environment variables)
- **Use HTTPS in production** - Auth0 requires HTTPS for token validation
- **Use minimal scopes** - Only request and enforce scopes your API actually needs
- **Keep packages updated** - Regularly update `Auth0.AspNetCore.Authentication.Api` for security patches
---
---
# Auth0 ASP.NET Core Web API Setup Guide
Setup instructions for ASP.NET Core Web API applications.
---
## Quick Setup (Automated)
Below uses the Auth0 CLI to create an Auth0 API resource and retrieve your credentials.
### Step 1: Install Auth0 CLI and create API resource
```bash
# Install Auth0 CLI (macOS)
brew install auth0
# Login
auth0 login --no-input
# Create an Auth0 API resource
auth0 apis create \
--name "My ASP.NET Core API" \
--identifier https://my-api.example.com \
--json
```
Note the `identifier` value - this is your Audience.
### Step 2: Add configuration
Once you have your Domain and Audience, add the following to `appsettings.json`:
```json
{
"Auth0": {
"Domain": "your-tenant.auth0.com",
"Audience": "https://my-api.example.com"
}
}
```
Replace `your-tenant.auth0.com` with your Auth0 tenant domain and `https://my-api.example.com` with the identifier you used when creating the API resource.
---
## Manual Setup
### Install Package
```bash
dotnet add package Auth0.AspNetCore.Authentication.Api
```
### Create Auth0 API Resource
1. Go to Auth0 Dashboard → Applications → APIs
2. Click **Create API**
3. Set a **Name** and an **Identifier** (e.g., `https://my-api.example.com`)
4. Note the Identifier - this is your `Audience`
### Configure appsettings.json
```json
{
"Auth0": {
"Domain": "your-tenant.auth0.com",
"Audience": "https://my-api.example.com"
}
}
```
**Important:** Domain format is `your-tenant.auth0.com` - do NOT include `https://`.
### Get Auth0 Configuration
- **Domain:** Auth0 Dashboard → Settings → Domain (or `auth0 tenants list`)
- **Audience:** The identifier you set when creating the API resource
### Using Environment Variables
For production/containers, set environment variables (these override appsettings.json):
```bash
export Auth0__Domain=your-tenant.auth0.com
export Auth0__Audience=https://my-api.example.com
```
Note the double underscore `__` separator for nested config in environment variables.
---
## Getting a Test Token
### Via Auth0 Dashboard
1. Go to Auth0 Dashboard → Applications → APIs
2. Select your API
3. Click the **Test** tab
4. Click **Copy Token** to get a test access token
### Via Auth0 CLI (Client Credentials)
```bash
# Get access token for testing
auth0 test token \
--audience https://my-api.example.com
```
### Via curl (Client Credentials Flow)
```bash
curl -X POST https://your-tenant.auth0.com/oauth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"audience": "https://my-api.example.com",
"grant_type": "client_credentials"
}'
```
---
## Troubleshooting
**401 Unauthorized - "invalid_token":** Verify that the `Audience` in config exactly matches your API Identifier in Auth0 Dashboard.
**401 Unauthorized - "invalid_issuer":** Ensure `Domain` does not include `https://` - use `your-tenant.auth0.com` format only.
**HTTPS certificate errors locally:** Run `dotnet dev-certs https --trust` to trust the development certificate.
**Token expired:** Test tokens from the Dashboard are short-lived. Request a fresh token.
---
SHA-256: 74e7004d74eab60a3720bae64d9c480431173d266fd8a75f433cdead62515678