|
1 | | -# Auth0 ASP.NET Core API - AI Agent Instructions |
| 1 | +# AI Agent Guidelines for Auth0.AspNetCore.Authentication.Api |
2 | 2 |
|
3 | | -This is an **Auth0 authentication SDK** for ASP.NET Core APIs providing **JWT Bearer authentication with DPoP (Demonstration of Proof-of-Possession) support**. It wraps `Microsoft.AspNetCore.Authentication.JwtBearer` with Auth0-specific configuration and RFC 9449 DPoP validation. |
| 3 | +See @CLAUDE.md for all coding guidelines, commands, project structure, code style, testing conventions, and boundaries. |
4 | 4 |
|
5 | | -## Architecture Overview |
6 | | - |
7 | | -### Core Design Pattern: Fluent Builder with Extension Point |
8 | | -- **Entry**: `ServiceCollectionExtensions.AddAuth0ApiAuthentication()` → returns `Auth0ApiAuthenticationBuilder` |
9 | | -- **DPoP**: Optional via `builder.WithDPoP()` - adds validation services and event handlers |
10 | | -- **Options**: `Auth0ApiOptions` wraps `JwtBearerOptions` + `Domain`; `DPoPOptions` configures DPoP behavior |
11 | | -- **Validation Pipeline**: JWT Bearer events → DPoP event handlers (MessageReceived, TokenValidation, Challenge) → `DPoPProofValidationService` |
12 | | - |
13 | | -### Key Components |
14 | | -- **`src/Auth0.AspNetCore.Authentication.Api/`**: Main library |
15 | | - - `ServiceCollectionExtensions.cs`: Primary API surface - `AddAuth0ApiAuthentication()` |
16 | | - - `AuthenticationBuilderExtensions.cs`: DPoP enablement via `.WithDPoP()`, internal JWT Bearer setup |
17 | | - - `Auth0ApiAuthenticationBuilder.cs`: Fluent builder returned from setup methods |
18 | | - - **`DPoP/`**: Complete RFC 9449 implementation |
19 | | - - `DPoPProofValidationService.cs`: Core validation logic (JWK extraction, signature, claims, thumbprint binding) |
20 | | - - `EventHandlers/`: Intercept JWT Bearer events to inject DPoP validation |
21 | | - - `DPoPOptions.cs`: Mode (`Allowed`/`Required`/`Disabled`), timing (`IatOffset`, `Leeway`) |
22 | | - |
23 | | -### DPoP Enforcement Modes |
24 | | -1. **Allowed** (default): Accept both Bearer and DPoP tokens - enables gradual migration |
25 | | -2. **Required**: Reject Bearer tokens, only accept DPoP - strict security |
26 | | -3. **Disabled**: Standard JWT Bearer only |
27 | | - |
28 | | -## Development Workflows |
29 | | - |
30 | | -### Building |
31 | | -```bash |
32 | | -dotnet restore Auth0.AspNetCore.Authentication.Api.sln |
33 | | -dotnet build Auth0.AspNetCore.Authentication.Api.sln --configuration Release |
34 | | -``` |
35 | | - |
36 | | -### Testing |
37 | | -```bash |
38 | | -# Unit tests (mocks, no Auth0 connection) |
39 | | -dotnet test tests/Auth0.AspNetCore.Authentication.Api.UnitTests/ |
40 | | - |
41 | | -# Integration tests (requires Auth0 environment variables - see .github/workflows/build.yml for required secrets) |
42 | | -dotnet test tests/Auth0.AspNetCore.Authentication.Api.IntegrationTests/ |
43 | | -``` |
44 | | - |
45 | | -**Integration test pattern**: `TestWebApplicationFactory` creates TestServer → `Auth0TokenHelper` obtains real tokens → `DPoPHelper` generates DPoP proofs with EC keys |
46 | | - |
47 | | -### Playground Testing |
48 | | -```bash |
49 | | -cd Auth0.AspNetCore.Authentication.Api.Playground |
50 | | -# Configure Auth0:Domain and Auth0:Audience in appsettings.json |
51 | | -dotnet run |
52 | | -# Open https://localhost:7190/swagger |
53 | | -``` |
54 | | -Use `Auth0.AspNetCore.Authentication.Api.Playground.postman_collection.json` for pre-configured API calls |
55 | | - |
56 | | -### Documentation Generation |
57 | | -```bash |
58 | | -./build-docs.sh # Builds project + runs docfx |
59 | | -# View: sudo docfx serve docs → http://localhost:8080 |
60 | | -``` |
61 | | - |
62 | | -## Critical Patterns & Conventions |
63 | | - |
64 | | -### Options Configuration Pattern |
65 | | -```csharp |
66 | | -// ALWAYS use this pattern - Auth0ApiOptions wraps JwtBearerOptions |
67 | | -builder.Services.AddAuth0ApiAuthentication(options => |
68 | | -{ |
69 | | - options.Domain = "tenant.auth0.com"; // NO https:// prefix |
70 | | - options.JwtBearerOptions = new JwtBearerOptions |
71 | | - { |
72 | | - Audience = "https://api-identifier", |
73 | | - // Any standard JWT Bearer option works here |
74 | | - }; |
75 | | -}); |
76 | | -``` |
77 | | - |
78 | | -### DPoP Header Validation Flow |
79 | | -1. **MessageReceived** event: Extract DPoP proof from `DPoP` header, access token from `Authorization: DPoP <token>` |
80 | | -2. **TokenValidated** event: Call `DPoPProofValidationService.ValidateAsync()` with `DPoPProofValidationParameters` |
81 | | -3. **Validation checks**: JWK extraction → signature verification → `cnf` claim thumbprint match → `htm`/`htu`/`iat` claim validation |
82 | | -4. **Challenge** event: Add `DPoP` to `WWW-Authenticate` on 401 failures |
83 | | - |
84 | | -### Event Handler Chaining |
85 | | -DPoP events **wrap** user-defined `JwtBearerEvents`. Example in `DPoPEventsFactory.Create()`: |
86 | | -```csharp |
87 | | -// Preserve user's custom event, execute DPoP handler first |
88 | | -Events.OnMessageReceived = async context => { |
89 | | - await dpopHandler.HandleMessageReceived(context); |
90 | | - if (userEvents?.OnMessageReceived != null) |
91 | | - await userEvents.OnMessageReceived(context); |
92 | | -}; |
93 | | -``` |
94 | | - |
95 | | -### Error Handling Convention |
96 | | -- DPoP errors use `Auth0Constants.DPoP.Error.Code.*` (e.g., `invalid_dpop_proof`, `invalid_request`) |
97 | | -- Always fail request with `context.Fail()` + descriptive error in `DPoPProofValidationResult` |
98 | | -- Log errors via `ILogger<T>` at ERROR level for failed validations |
99 | | - |
100 | | -## Testing Guidelines |
101 | | - |
102 | | -### Unit Tests |
103 | | -- Use xUnit `[Fact]` and `[Theory]` |
104 | | -- Mock `IDPoPProofValidationService` for event handler tests |
105 | | -- Test each DPoP validator independently (see `DPoPProofValidationService.cs` internal methods) |
106 | | - |
107 | | -### Integration Tests |
108 | | -- Inherit from `IAsyncLifetime` for test setup/teardown |
109 | | -- Use `Auth0Scenario` enum to configure different test environments (Basic, DPoPAllowed, DPoPRequired) |
110 | | -- **Never hardcode tokens** - use `Auth0TokenHelper.GetClientCredentialsTokenAsync()` with environment variables |
111 | | -- DPoP tests must create real EC keys: `ECDsa.Create(ECCurve.NamedCurves.nistP256)` |
112 | | - |
113 | | -## Common Pitfalls |
114 | | - |
115 | | -1. **Domain format**: Must be `tenant.auth0.com` NOT `https://tenant.auth0.com` - code auto-prepends `https://` |
116 | | -2. **InternalsVisibleTo**: Tests access internal validators - declared in `.csproj` `<InternalsVisibleTo>` |
117 | | -3. **DPoP mode confusion**: `Allowed` mode validates DPoP IF present, `Required` mode rejects Bearer tokens entirely |
118 | | -4. **Event preservation**: When modifying `AuthenticationBuilderExtensions.cs`, ALWAYS preserve existing user events via `JwtBearerEventsFactory.CreateBaseEvents()` |
119 | | -5. **Token validation timing**: Use `IatOffset` (default 300s) for clock skew, `Leeway` (default 30s) for lifetime checks |
120 | | - |
121 | | -## File Organization |
122 | | - |
123 | | -``` |
124 | | -src/Auth0.AspNetCore.Authentication.Api/ |
125 | | - ├── ServiceCollectionExtensions.cs # IServiceCollection.AddAuth0ApiAuthentication() |
126 | | - ├── AuthenticationBuilderExtensions.cs # AuthenticationBuilder.AddAuth0ApiAuthentication(), .WithDPoP() |
127 | | - ├── Auth0ApiAuthenticationBuilder.cs # Fluent builder |
128 | | - ├── Auth0ApiOptions.cs # Domain + JwtBearerOptions wrapper |
129 | | - ├── Auth0JwtBearerPostConfigureOptions.cs # IPostConfigureOptions - sets Authority from Domain |
130 | | - └── DPoP/ |
131 | | - ├── DPoPProofValidationService.cs # Core RFC 9449 implementation |
132 | | - ├── DPoPOptions.cs # Mode, IatOffset, Leeway |
133 | | - ├── DPoPEventHandlers.cs # Coordinates MessageReceived, TokenValidated, Challenge |
134 | | - └── EventHandlers/ # Individual event handler implementations |
135 | | -``` |
136 | | - |
137 | | -## Auth0-Specific Behaviors |
138 | | - |
139 | | -- **Authority construction**: Automatically creates `https://{Domain}` from `options.Domain` |
140 | | -- **Audience validation**: Uses standard JWT Bearer audience validation (not Auth0-specific) |
141 | | -- **Scope claims**: Auth0 includes scopes in `scope` claim as space-separated string (see `EXAMPLES.md`) |
142 | | -- **DPoP support**: Auth0 DPoP tokens have `cnf.jkt` claim with JWK thumbprint (SHA-256 of public key) |
143 | | - |
144 | | -## When Modifying Code |
145 | | - |
146 | | -### Adding New DPoP Validators |
147 | | -1. Create internal method in `DPoPProofValidationService.cs` |
148 | | -2. Call from `ValidateAsync()` pipeline |
149 | | -3. Set errors via `result.SetError(code, description)` using constants from `Auth0Constants.DPoP.Error` |
150 | | -4. Add unit tests in `Auth0.AspNetCore.Authentication.Api.UnitTests` |
151 | | - |
152 | | -### Changing DPoP Modes |
153 | | -- Update `DPoPModes` enum |
154 | | -- Modify `MessageReceivedHandler.cs` and `TokenValidationHandler.cs` switch statements |
155 | | -- Add mode-specific tests in `tests/Auth0.AspNetCore.Authentication.Api.IntegrationTests/` |
156 | | - |
157 | | -### Package Updates |
158 | | -- Version in `Directory.Build.props` (`<VersionPrefix>`) |
159 | | -- Release notes URL in `Auth0.AspNetCore.Authentication.Api.csproj` (`<PackageReleaseNotes>`) |
160 | | -- Target framework is .NET 8.0+ only (no multi-targeting) |
161 | | - |
162 | | -## Key Files for Understanding Features |
163 | | - |
164 | | -- **Migration scenarios**: `MIGRATION.md` - 8 before/after examples |
165 | | -- **Usage patterns**: `EXAMPLES.md` - 16 copy-paste scenarios |
166 | | -- **DPoP validation**: `src/Auth0.AspNetCore.Authentication.Api/DPoP/DPoPProofValidationService.cs` (515 lines) |
167 | | -- **Setup flow**: `src/Auth0.AspNetCore.Authentication.Api/AuthenticationBuilderExtensions.cs:ConfigureJwtBearerOptions()` |
| 5 | +This file exists so that non-Claude AI agents (Codex CLI, Gemini CLI, etc.) read the same instructions. All guidelines are maintained in a single place (`CLAUDE.md`) to avoid duplication and drift. |
0 commit comments