Auth Flow
This page describes the end-to-end authentication flow for both production (Auth0) and local development (user-jwts) scenarios.
Production Auth Flow (Auth0)
sequenceDiagram
actor User
participant Client
participant Auth0
participant API
User->>Client: Login
Client->>Auth0: POST /oauth/token<br/>(client credentials or authorization code)
Auth0-->>Client: Access token (JWT)
Client->>API: GET /endpoint<br/>Authorization: Bearer <token>
API->>API: Decode token — read iss claim
Note over API: iss = https://your-tenant.auth0.com/
API->>API: Forward to Auth0 scheme handler
API->>Auth0: Fetch JWKS (public keys)<br/>GET /well-known/jwks.json
Note over API,Auth0: Keys are cached after first fetch
Auth0-->>API: JSON Web Key Set
API->>API: Validate signature, issuer,<br/>audience & expiry
API-->>Client: 200 OK + response body
Key points
- The API never stores or sees the user's password — Auth0 handles all credential management.
- The JWKS (public key set) is fetched from Auth0 on first use and cached — subsequent requests don't hit Auth0.
- If the token is invalid or expired, the API returns
401 Unauthorizedwith aWWW-Authenticateheader describing the failure.
Local Development Auth Flow (user-jwts)
sequenceDiagram
actor Dev
participant CLI as dotnet user-jwts
participant Secrets as User Secrets
participant API
Dev->>CLI: dotnet user-jwts create --scheme UserJwts
CLI->>Secrets: Store signing key
CLI-->>Dev: Bearer token (JWT)
Dev->>API: GET /endpoint<br/>Authorization: Bearer <token>
API->>API: Decode token — read iss claim
Note over API: iss = dotnet-user-jwts
API->>API: Forward to UserJwts scheme handler
API->>Secrets: Load signing key
API->>API: Validate signature, audience & expiry
API-->>Dev: 200 OK + response body
Key points
- No network calls are made — everything is validated locally using the signing key from user secrets.
- The signing key never leaves your machine and is not in source control.
- This scheme is only registered in the Development environment — it cannot be used against staging or production.
Scheme Routing Flow
When any request arrives with a Bearer token, the API runs a routing step before any validation occurs:
flowchart TD
A[Incoming request] --> B{Has Authorization header?}
B -- No --> Z[401 Unauthorized]
B -- Yes --> C[Decode JWT header + payload\nno validation yet]
C --> D{Read iss claim}
D -- dotnet-user-jwts --> E{Is Development\nenvironment?}
E -- Yes --> F[UserJwts handler\nValidate with user secrets key]
E -- No --> Z
D -- https://your-tenant.auth0.com/ --> G[Auth0 handler\nValidate with JWKS]
D -- Unknown issuer --> G
F -- Valid --> H[200 OK]
G -- Valid --> H
F -- Invalid --> Z
G -- Invalid --> Z
note
Unknown or unexpected issuers fall through to the Auth0 handler, which will reject them. There is no silent failure — an unrecognised issuer always results in a 401.
Token Anatomy
Both Auth0 and user-jwts produce standard JWTs. Here's what a decoded local dev token looks like:
Header
{
"alg": "HS256",
"typ": "JWT"
}
Payload
{
"sub": "your-name",
"iss": "dotnet-user-jwts",
"aud": [
"https://localhost:7251"
],
"iat": 1712000000,
"exp": 1727000000,
"jti": "d2b8240f"
}
And a typical Auth0 token payload:
{
"sub": "auth0|abc123",
"iss": "https://your-tenant.auth0.com/",
"aud": "https://your-api-identifier",
"iat": 1712000000,
"exp": 1712003600,
"azp": "client-id",
"scope": "openid profile email"
}
Key differences
| Claim | Auth0 token | Local dev token |
|---|---|---|
iss | https://your-tenant.auth0.com/ | dotnet-user-jwts |
aud | API identifier (single string) | localhost URLs (array) |
exp | Typically 1 hour | Typically 6 months |
sub | Auth0 user ID | Name you passed to CLI |
| Signing | RS256 (asymmetric) | HS256 (symmetric) |
Error Responses
| Scenario | HTTP Status | error | error_description |
|---|---|---|---|
| No token provided | 401 | invalid_token | — |
| Token expired | 401 | invalid_token | The token is expired |
| Bad signature | 401 | invalid_token | The signature key was not found |
| Wrong audience | 401 | invalid_token | The audience is invalid |
| Wrong issuer | 401 | invalid_token | The issuer is invalid |
| Insufficient scope/role | 403 | — | — |