Skip to main content

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 Unauthorized with a WWW-Authenticate header 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:

{
"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

ClaimAuth0 tokenLocal dev token
isshttps://your-tenant.auth0.com/dotnet-user-jwts
audAPI identifier (single string)localhost URLs (array)
expTypically 1 hourTypically 6 months
subAuth0 user IDName you passed to CLI
SigningRS256 (asymmetric)HS256 (symmetric)

Error Responses

ScenarioHTTP Statuserrorerror_description
No token provided401invalid_token
Token expired401invalid_tokenThe token is expired
Bad signature401invalid_tokenThe signature key was not found
Wrong audience401invalid_tokenThe audience is invalid
Wrong issuer401invalid_tokenThe issuer is invalid
Insufficient scope/role403