diff --git a/AGENTS.md b/AGENTS.md index 55562fd..ed88bbe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,167 +1,5 @@ -# Auth0 ASP.NET Core API - AI Agent Instructions +# AI Agent Guidelines for Auth0.AspNetCore.Authentication.Api -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. +See @CLAUDE.md for all coding guidelines, commands, project structure, code style, testing conventions, and boundaries. -## Architecture Overview - -### Core Design Pattern: Fluent Builder with Extension Point -- **Entry**: `ServiceCollectionExtensions.AddAuth0ApiAuthentication()` → returns `Auth0ApiAuthenticationBuilder` -- **DPoP**: Optional via `builder.WithDPoP()` - adds validation services and event handlers -- **Options**: `Auth0ApiOptions` wraps `JwtBearerOptions` + `Domain`; `DPoPOptions` configures DPoP behavior -- **Validation Pipeline**: JWT Bearer events → DPoP event handlers (MessageReceived, TokenValidation, Challenge) → `DPoPProofValidationService` - -### Key Components -- **`src/Auth0.AspNetCore.Authentication.Api/`**: Main library - - `ServiceCollectionExtensions.cs`: Primary API surface - `AddAuth0ApiAuthentication()` - - `AuthenticationBuilderExtensions.cs`: DPoP enablement via `.WithDPoP()`, internal JWT Bearer setup - - `Auth0ApiAuthenticationBuilder.cs`: Fluent builder returned from setup methods - - **`DPoP/`**: Complete RFC 9449 implementation - - `DPoPProofValidationService.cs`: Core validation logic (JWK extraction, signature, claims, thumbprint binding) - - `EventHandlers/`: Intercept JWT Bearer events to inject DPoP validation - - `DPoPOptions.cs`: Mode (`Allowed`/`Required`/`Disabled`), timing (`IatOffset`, `Leeway`) - -### DPoP Enforcement Modes -1. **Allowed** (default): Accept both Bearer and DPoP tokens - enables gradual migration -2. **Required**: Reject Bearer tokens, only accept DPoP - strict security -3. **Disabled**: Standard JWT Bearer only - -## Development Workflows - -### Building -```bash -dotnet restore Auth0.AspNetCore.Authentication.Api.sln -dotnet build Auth0.AspNetCore.Authentication.Api.sln --configuration Release -``` - -### Testing -```bash -# Unit tests (mocks, no Auth0 connection) -dotnet test tests/Auth0.AspNetCore.Authentication.Api.UnitTests/ - -# Integration tests (requires Auth0 environment variables - see .github/workflows/build.yml for required secrets) -dotnet test tests/Auth0.AspNetCore.Authentication.Api.IntegrationTests/ -``` - -**Integration test pattern**: `TestWebApplicationFactory` creates TestServer → `Auth0TokenHelper` obtains real tokens → `DPoPHelper` generates DPoP proofs with EC keys - -### Playground Testing -```bash -cd Auth0.AspNetCore.Authentication.Api.Playground -# Configure Auth0:Domain and Auth0:Audience in appsettings.json -dotnet run -# Open https://localhost:7190/swagger -``` -Use `Auth0.AspNetCore.Authentication.Api.Playground.postman_collection.json` for pre-configured API calls - -### Documentation Generation -```bash -./build-docs.sh # Builds project + runs docfx -# View: sudo docfx serve docs → http://localhost:8080 -``` - -## Critical Patterns & Conventions - -### Options Configuration Pattern -```csharp -// ALWAYS use this pattern - Auth0ApiOptions wraps JwtBearerOptions -builder.Services.AddAuth0ApiAuthentication(options => -{ - options.Domain = "tenant.auth0.com"; // NO https:// prefix - options.JwtBearerOptions = new JwtBearerOptions - { - Audience = "https://api-identifier", - // Any standard JWT Bearer option works here - }; -}); -``` - -### DPoP Header Validation Flow -1. **MessageReceived** event: Extract DPoP proof from `DPoP` header, access token from `Authorization: DPoP ` -2. **TokenValidated** event: Call `DPoPProofValidationService.ValidateAsync()` with `DPoPProofValidationParameters` -3. **Validation checks**: JWK extraction → signature verification → `cnf` claim thumbprint match → `htm`/`htu`/`iat` claim validation -4. **Challenge** event: Add `DPoP` to `WWW-Authenticate` on 401 failures - -### Event Handler Chaining -DPoP events **wrap** user-defined `JwtBearerEvents`. Example in `DPoPEventsFactory.Create()`: -```csharp -// Preserve user's custom event, execute DPoP handler first -Events.OnMessageReceived = async context => { - await dpopHandler.HandleMessageReceived(context); - if (userEvents?.OnMessageReceived != null) - await userEvents.OnMessageReceived(context); -}; -``` - -### Error Handling Convention -- DPoP errors use `Auth0Constants.DPoP.Error.Code.*` (e.g., `invalid_dpop_proof`, `invalid_request`) -- Always fail request with `context.Fail()` + descriptive error in `DPoPProofValidationResult` -- Log errors via `ILogger` at ERROR level for failed validations - -## Testing Guidelines - -### Unit Tests -- Use xUnit `[Fact]` and `[Theory]` -- Mock `IDPoPProofValidationService` for event handler tests -- Test each DPoP validator independently (see `DPoPProofValidationService.cs` internal methods) - -### Integration Tests -- Inherit from `IAsyncLifetime` for test setup/teardown -- Use `Auth0Scenario` enum to configure different test environments (Basic, DPoPAllowed, DPoPRequired) -- **Never hardcode tokens** - use `Auth0TokenHelper.GetClientCredentialsTokenAsync()` with environment variables -- DPoP tests must create real EC keys: `ECDsa.Create(ECCurve.NamedCurves.nistP256)` - -## Common Pitfalls - -1. **Domain format**: Must be `tenant.auth0.com` NOT `https://tenant.auth0.com` - code auto-prepends `https://` -2. **InternalsVisibleTo**: Tests access internal validators - declared in `.csproj` `` -3. **DPoP mode confusion**: `Allowed` mode validates DPoP IF present, `Required` mode rejects Bearer tokens entirely -4. **Event preservation**: When modifying `AuthenticationBuilderExtensions.cs`, ALWAYS preserve existing user events via `JwtBearerEventsFactory.CreateBaseEvents()` -5. **Token validation timing**: Use `IatOffset` (default 300s) for clock skew, `Leeway` (default 30s) for lifetime checks - -## File Organization - -``` -src/Auth0.AspNetCore.Authentication.Api/ - ├── ServiceCollectionExtensions.cs # IServiceCollection.AddAuth0ApiAuthentication() - ├── AuthenticationBuilderExtensions.cs # AuthenticationBuilder.AddAuth0ApiAuthentication(), .WithDPoP() - ├── Auth0ApiAuthenticationBuilder.cs # Fluent builder - ├── Auth0ApiOptions.cs # Domain + JwtBearerOptions wrapper - ├── Auth0JwtBearerPostConfigureOptions.cs # IPostConfigureOptions - sets Authority from Domain - └── DPoP/ - ├── DPoPProofValidationService.cs # Core RFC 9449 implementation - ├── DPoPOptions.cs # Mode, IatOffset, Leeway - ├── DPoPEventHandlers.cs # Coordinates MessageReceived, TokenValidated, Challenge - └── EventHandlers/ # Individual event handler implementations -``` - -## Auth0-Specific Behaviors - -- **Authority construction**: Automatically creates `https://{Domain}` from `options.Domain` -- **Audience validation**: Uses standard JWT Bearer audience validation (not Auth0-specific) -- **Scope claims**: Auth0 includes scopes in `scope` claim as space-separated string (see `EXAMPLES.md`) -- **DPoP support**: Auth0 DPoP tokens have `cnf.jkt` claim with JWK thumbprint (SHA-256 of public key) - -## When Modifying Code - -### Adding New DPoP Validators -1. Create internal method in `DPoPProofValidationService.cs` -2. Call from `ValidateAsync()` pipeline -3. Set errors via `result.SetError(code, description)` using constants from `Auth0Constants.DPoP.Error` -4. Add unit tests in `Auth0.AspNetCore.Authentication.Api.UnitTests` - -### Changing DPoP Modes -- Update `DPoPModes` enum -- Modify `MessageReceivedHandler.cs` and `TokenValidationHandler.cs` switch statements -- Add mode-specific tests in `tests/Auth0.AspNetCore.Authentication.Api.IntegrationTests/` - -### Package Updates -- Version in `Directory.Build.props` (``) -- Release notes URL in `Auth0.AspNetCore.Authentication.Api.csproj` (``) -- Target framework is .NET 8.0+ only (no multi-targeting) - -## Key Files for Understanding Features - -- **Migration scenarios**: `MIGRATION.md` - 8 before/after examples -- **Usage patterns**: `EXAMPLES.md` - 16 copy-paste scenarios -- **DPoP validation**: `src/Auth0.AspNetCore.Authentication.Api/DPoP/DPoPProofValidationService.cs` (515 lines) -- **Setup flow**: `src/Auth0.AspNetCore.Authentication.Api/AuthenticationBuilderExtensions.cs:ConfigureJwtBearerOptions()` +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. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..db275fa --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,156 @@ +# AI Agent Guidelines for Auth0.AspNetCore.Authentication.Api + +This document provides context and guidelines for AI coding assistants working with the Auth0.AspNetCore.Authentication.Api codebase. + +## Your Role + +You are a C# SDK engineer maintaining the Auth0 ASP.NET Core API authentication library. It wraps `Microsoft.AspNetCore.Authentication.JwtBearer` with Auth0-specific configuration, RFC 9449 DPoP (Demonstration of Proof-of-Possession) validation, and Multiple Custom Domain support; you write small, well-tested code and preserve the fluent-builder public API that consumers depend on. + +--- + +## Working Principles + +Apply these on every task in this repo — they keep changes correct, small, and reviewable. + +- **Think before coding.** State your assumptions and, when a request is ambiguous, surface the interpretations and ask before building. Recommend a simpler approach when you see one. A clarifying question up front beats a wrong implementation. +- **Simplicity first.** Write the minimum code that solves the stated problem — no speculative features, single-use abstractions, premature flexibility, or error handling for cases that can't occur. +- **Surgical changes.** Touch only what the request requires. Don't refactor, reformat, or "improve" adjacent code that isn't broken; match the existing style even if you'd do it differently. Every changed line should trace directly to the request. Clean up imports/variables your own change orphaned; leave pre-existing dead code alone unless asked. +- **Goal-driven execution.** Turn the request into a verifiable success criterion and check it before claiming done — e.g. "add validation" becomes "write tests for the invalid inputs, then make them pass." Don't report success you haven't verified. + +--- + +## Project Overview + +**Auth0.AspNetCore.Authentication.Api** is an Auth0 authentication SDK for ASP.NET Core APIs providing JWT Bearer authentication with built-in DPoP and Multiple Custom Domain support. + +- **Language:** C# (`latest`, nullable + implicit usings enabled) +- **Tech Stack:** ASP.NET Core JWT Bearer authentication; DPoP per RFC 9449 +- **Package Manager:** NuGet (.NET SDK) +- **Minimum Platform Version:** .NET 8.0 — library multi-targets `net8.0;net10.0` +- **Dependencies:** `Microsoft.AspNetCore.Authentication.JwtBearer` (8.0.27 / 10.0.3), `Microsoft.Extensions.Logging.Abstractions` · test: xUnit 2.9, Moq 4.20, FluentAssertions 7.2. See the `.csproj` files for the authoritative list. + +--- + +## Project Structure + +``` +. +├── src/Auth0.AspNetCore.Authentication.Api/ # Main library (the published NuGet package) +│ ├── ServiceCollectionExtensions.cs # Public API: IServiceCollection.AddAuth0ApiAuthentication() +│ ├── AuthenticationBuilderExtensions.cs # Public API: .AddAuth0ApiAuthentication(), .WithDPoP() +│ ├── Auth0ApiAuthenticationBuilder.cs # Fluent builder returned from setup +│ ├── Auth0ApiOptions.cs # Domain + JwtBearerOptions wrapper +│ ├── Auth0JwtBearerPostConfigureOptions.cs # Sets Authority from Domain; adds Auth0-Client header +│ ├── Utils.cs / Version.cs # Telemetry agent string + SDK version constant +│ ├── DPoP/ # RFC 9449 implementation (validation, modes, event handlers) +│ └── CustomDomains/ # Multiple Custom Domain configuration + caching +├── tests/ +│ ├── Auth0.AspNetCore.Authentication.Api.UnitTests/ # xUnit unit tests (no credentials) +│ └── Auth0.AspNetCore.Authentication.Api.IntegrationTests/ # Live-tenant integration tests +├── Auth0.AspNetCore.Authentication.Api.Playground/ # Runnable sample API + Swagger +├── Auth0.AspNetCore.Authentication.Api.Playground.DPoPClient/ # Sample DPoP client +├── build/common.props # Shared package metadata + version +├── docs-source/ + docs/ # docfx source and generated API docs +└── .version / .shiprc # Release version source + ship-cli config +``` + +### Key Files + +| File | Purpose | +|------|---------| +| `src/.../ServiceCollectionExtensions.cs` | Primary public entry — `AddAuth0ApiAuthentication()` overloads | +| `src/.../AuthenticationBuilderExtensions.cs` | DPoP enablement `.WithDPoP()` + internal JWT Bearer setup | +| `src/.../DPoP/DPoPProofValidationService.cs` | Core DPoP proof validation (JWK, signature, `cnf` thumbprint, `htm`/`htu`/`iat`) | +| `src/.../DPoP/DPoPModes.cs` | `Allowed` / `Required` / `Disabled` enforcement modes | +| `src/.../Utils.cs` + `Version.cs` | `Auth0-Client` telemetry header payload + SDK version | +| `.version`, `build/common.props`, `Version.cs` | The three version sources kept in sync via `.shiprc` | + +--- + +## Boundaries + +### ✅ Always Do + +- Run the unit tests before committing (`dotnet test tests/…UnitTests/`). +- Follow the existing code style and naming conventions (see `.editorconfig` — enforced: 4-space indent, `var` for built-in/apparent types, braces required, 120-col guideline). +- Add xUnit tests for new functionality. +- Set DPoP validation errors via the typed `Auth0Constants.DPoP.Error.Code.*` constants and `DPoPProofValidationResult.SetError(...)` — do not throw ad-hoc exceptions from the validation pipeline. +- Update `README.md` and `EXAMPLES.md` in the same PR when you change the public API, configuration options, or supported integration patterns. +- Keep the three version sources (`.version`, `build/common.props` ``, `src/.../Version.cs`) in sync — they are listed together in `.shiprc`. +- Telemetry is shared per-request infrastructure — existing request paths already carry it. Only when you add a **new outbound HTTP path to Auth0**, route it through `Utils.CreateAgentString()` and add the `Auth0-Client` header (as `Auth0JwtBearerPostConfigureOptions.cs` and `CustomDomains/Auth0CustomDomainsConfigurationManager.cs` do) rather than hand-rolling a new client. + +### ⚠️ Ask First + +- **Any breaking change — always ask first.** Never change or remove a public API signature, the fluent-builder surface, or default DPoP behavior on your own initiative. Breaking changes also require a migration guide (inferred from the target branch/major at that time). +- Adding new NuGet dependencies or bumping existing package versions. +- Modifying public API signatures (the `AddAuth0ApiAuthentication` / `WithDPoP` / `WithCustomDomains` fluent surface). +- Changes to CI/CD configuration (`.github/workflows/`). +- Modifying security-related code (DPoP proof validation, token/thumbprint handling, custom-domain trust). +- Running the integration/acceptance tests — they hit a live Auth0 tenant, are slow, and require real credentials (see Testing). + +### 🚫 Never Do + +- Commit secrets, API keys, tokens, or real tenant credentials. +- Hardcode tokens in tests — obtain them via `Auth0TokenHelper.GetClientCredentialsTokenAsync()` with environment variables. +- Modify auto-generated files or the `docs/` generated output by hand. +- Remove or skip failing tests without fixing the underlying issue. +- Modify build/vendor output directories (`bin/`, `obj/`, `TestResults/`). +- Break backward compatibility without asking first and getting explicit approval. + +--- + +## Security Considerations + +- **DPoP (RFC 9449):** `DPoPProofValidationService` verifies the proof JWK, the signature, the `cnf.jkt` thumbprint binding (SHA-256 of the public key), and the `htm`/`htu`/`iat` claims. `IatOffset` (default 300s) covers clock skew; `Leeway` (default 30s) covers lifetime checks. Treat this pipeline as security-critical — changes are Ask-First. +- **Token handling:** never log access tokens or DPoP proofs. Log validation *failures* via `ILogger` with the error code/description, not token contents. +- **DPoP modes:** `Allowed` validates DPoP if present (enables migration); `Required` rejects Bearer tokens entirely; `Disabled` is standard JWT Bearer. Don't silently change the default (`Allowed`). +- **Custom domains:** token-issuer trust is validated per configured domain — review `CustomDomains/` trust logic carefully before changing. +- **Secret scanning:** Snyk (`.github/workflows/snyk.yml`) and RL-Secure (`rl-secure.yml`) run in CI. Never commit anything that trips them. + +--- + +> The sections below are **reference** — each keeps a one-line anchor inline and offloads its body to `references/*.md` behind a linked pointer. + +## Commands + +Core loop: `dotnet restore … && dotnet build …sln -c Release` then `dotnet test tests/…UnitTests/`. See [references/commands.md](references/commands.md) for the full build/test/coverage/docs command list — read when you need to build, test, or package. + +--- + +## Testing + +- **Framework:** xUnit (`[Fact]`/`[Theory]`), Moq for mocking, FluentAssertions for assertions. +- **Location:** `tests/…UnitTests/` (safe, no credentials) and `tests/…IntegrationTests/` (live tenant). +- **Coverage:** Coverlet → Cobertura, uploaded to Codecov in CI. + +The default `dotnet test tests/…UnitTests/` suite is unit-only and needs no credentials. The integration suite hits a live Auth0 tenant and requires `BASIC_*`, `DPOP_ALLOWED_*`, `DPOP_REQUIRED_*`, and `CUSTOM_DOMAIN_*_*` environment variables — it is **Ask First** (see Boundaries). + +See [references/testing.md](references/testing.md) for conventions, mocking, the integration-test helpers, and the exact live-test command — read when writing or running tests. + +--- + +## Code Style + +Enforced via `.editorconfig`: 4-space indent, LF, UTF-8, 120-col guideline; `var` for built-in/apparent types (not elsewhere), braces always required, `System` usings sorted first with separated import groups. Public types/members use PascalCase and carry XML doc comments; the package is `CLSCompliant`. + +See [references/code-style.md](references/code-style.md) for naming detail, good/bad examples, and the dominant patterns (fluent builder, options, event-handler wrapping) — read before writing new source. + +--- + +## Git Workflow + +Branch from `master`; PRs target `master` and use `.github/PULL_REQUEST_TEMPLATE.md` (Changes / References / Testing / Checklist — all commits must be signed). See [references/git-workflow.md](references/git-workflow.md) for branch naming, commit style, and the CHANGELOG format — read when branching, committing, or opening a PR. + +--- + +## Common Pitfalls + +The top gotcha: `Domain` must be `tenant.auth0.com` **without** the `https://` prefix — the SDK prepends it. See [references/pitfalls.md](references/pitfalls.md) for the full list (DPoP mode semantics, event preservation, `InternalsVisibleTo`, multi-targeting) — read when debugging unexpected auth behavior. + +--- + +## Docs Update Rules + +> Treat documentation as a first-class deliverable. A PR that adds or changes public API, configuration, or integration patterns is **not complete** until the relevant docs are updated in the same PR. + +Tracked docs: `README.md` (present) and `EXAMPLES.md` (present) are the always-tracked docs; the `Playground` sample apps demonstrate the public API. See [references/docs-update.md](references/docs-update.md) for the tracked-docs inventory and the code-to-docs mapping — read when your change touches the public API, options, or integration patterns. diff --git a/references/code-style.md b/references/code-style.md new file mode 100644 index 0000000..e137953 --- /dev/null +++ b/references/code-style.md @@ -0,0 +1,47 @@ +# Code Style + +Style is enforced via `.editorconfig` at the repo root. Highlights: + +- 4-space indentation, LF line endings, UTF-8, final newline, trimmed trailing whitespace. +- 120-column guideline (`max_line_length = 120`). +- `var` for built-in types and when the type is apparent; **explicit type elsewhere** (`csharp_style_var_elsewhere = false`). +- Braces always required (`csharp_prefer_braces = true`). +- `System.*` usings sorted first, import directive groups separated. +- No `this.` qualification for fields/properties/methods/events. +- Expression-bodied members are **not** preferred for methods/properties/ctors. + +## Naming + +- PascalCase for public types, methods, properties; camelCase for locals/parameters; `_camelCase` is not used for private fields in this codebase (fields are unqualified). +- Public API types and members carry XML `` doc comments (the package generates a documentation file and is `CLSCompliant`). + +## ✅ Good + +```csharp +public static Auth0ApiAuthenticationBuilder AddAuth0ApiAuthentication( + this IServiceCollection services, + Action configureOptions, + Action? configureJwtBearer = null) +{ + ArgumentNullException.ThrowIfNull(configureOptions, nameof(configureOptions)); + + return services.AddAuth0ApiAuthentication( + Auth0Constants.AuthenticationScheme.Auth0, configureOptions, configureJwtBearer); +} +``` + +## ❌ Bad + +```csharp +// Missing null guard, no XML doc, expression-bodied public method, implicit var for non-apparent type +public static Auth0ApiAuthenticationBuilder AddAuth0ApiAuthentication(this IServiceCollection services, Action configureOptions) + => services.AddAuth0ApiAuthentication(Auth0Constants.AuthenticationScheme.Auth0, configureOptions, null); +``` + +## Dominant patterns + +- **Fluent builder** — setup methods return `Auth0ApiAuthenticationBuilder`, chained with `.WithDPoP()`. +- **Options pattern** — `Auth0ApiOptions` wraps `JwtBearerOptions` + `Domain`; `DPoPOptions` configures DPoP; validated via `IPostConfigureOptions` (`Auth0JwtBearerPostConfigureOptions`). +- **Event-handler wrapping** — DPoP event handlers wrap user-supplied `JwtBearerEvents`, running the DPoP logic first then delegating to the user's handler (see `DPoPEventsFactory.Create()` / `JwtBearerEventsFactory`). Always preserve existing user events when modifying this. +- **Typed error codes** — DPoP failures use `Auth0Constants.DPoP.Error.Code.*` set on `DPoPProofValidationResult`, surfaced via `context.Fail()`. +- **Argument guards** — public methods start with `ArgumentNullException.ThrowIfNull` / `ArgumentException.ThrowIfNullOrWhiteSpace`. diff --git a/references/commands.md b/references/commands.md new file mode 100644 index 0000000..16b8e3d --- /dev/null +++ b/references/commands.md @@ -0,0 +1,48 @@ +# Commands + +> Copy-paste ready. These match the CI workflow (`.github/workflows/build.yml`). + +```bash +# Restore +dotnet restore Auth0.AspNetCore.Authentication.Api.sln + +# Build (Release, as in CI) +dotnet build Auth0.AspNetCore.Authentication.Api.sln --configuration Release --no-restore + +# Run unit tests (safe — no credentials required) +dotnet test tests/Auth0.AspNetCore.Authentication.Api.UnitTests/Auth0.AspNetCore.Authentication.Api.UnitTests.csproj + +# Run unit tests with coverage (as in CI) +dotnet test tests/Auth0.AspNetCore.Authentication.Api.UnitTests/Auth0.AspNetCore.Authentication.Api.UnitTests.csproj \ + --collect:"XPlat Code coverage" --results-directory ./TestResults/ \ + /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura + +# Run a single test by name +dotnet test tests/Auth0.AspNetCore.Authentication.Api.UnitTests/ --filter "FullyQualifiedName~UtilsTests" + +# Clean +dotnet clean Auth0.AspNetCore.Authentication.Api.sln +``` + +## Integration tests (Ask First — live tenant) + +Requires the environment variables set as CI secrets (`BASIC_*`, `DPOP_ALLOWED_*`, `DPOP_REQUIRED_*`, `CUSTOM_DOMAIN_1_*`, `CUSTOM_DOMAIN_2_*`): + +```bash +dotnet test tests/Auth0.AspNetCore.Authentication.Api.IntegrationTests/Auth0.AspNetCore.Authentication.Api.IntegrationTests.csproj +``` + +## Playground + +```bash +cd Auth0.AspNetCore.Authentication.Api.Playground +# Configure Auth0:Domain and Auth0:Audience in appsettings.json first +dotnet run +# then open https://localhost:7190/swagger +``` + +## Documentation + +```bash +./build-docs.sh # builds the project + runs docfx into docs/ +``` diff --git a/references/docs-update.md b/references/docs-update.md new file mode 100644 index 0000000..03da6a2 --- /dev/null +++ b/references/docs-update.md @@ -0,0 +1,27 @@ +# Docs Update Rules + +This is a **library / SDK** — its public surface is exported types and methods (the `AddAuth0ApiAuthentication` overloads, `.WithDPoP()`, `.WithCustomDomains()`, `Auth0ApiOptions`, `DPoPOptions`, `DPoPModes`, custom-domain options). Keep docs in step with that surface. + +## Tracked docs + +| Doc | Covers | Exists | +|-----|--------|--------| +| `README.md` | Features, requirements, installation, getting started, DPoP, Multiple Custom Domains, configuration options | present | +| `EXAMPLES.md` | Copy-paste usage scenarios (basic setup, DPoP modes, custom domains, full JWT Bearer options) | present | +| `Playground/` + `Playground.DPoPClient/` | Runnable sample API + DPoP client demonstrating the public API | present | + +> `MIGRATION.md` exists but is **not tracked as a fixed doc** — migration guidance is version-specific and inferred from the target branch when a breaking change lands. `CHANGELOG.md` is maintained by the release flow, not during feature PRs. + +## When you change code, update these docs + +| When this changes | Update these docs | +|-------------------|-------------------| +| Public API surface (`AddAuth0ApiAuthentication`, `.WithDPoP()`, exported options types) | `README.md` (getting started / configuration), `EXAMPLES.md` (all affected samples), Playground apps that use it | +| Configuration options (`Auth0ApiOptions`, `DPoPOptions`, custom-domain options, appsettings keys) | `README.md` (configuration section) | +| Authentication / DPoP validation flow or modes | `README.md` (DPoP section), `EXAMPLES.md` (DPoP examples) | +| Install / package name / target frameworks | `README.md` (installation / requirements) | +| A new public method or exported type added | `EXAMPLES.md` (add a usage sample) | +| A public method or type removed or renamed | `README.md` + `EXAMPLES.md` (remove/update references) | +| New integration pattern supported (e.g. new custom-domain scenario) | `EXAMPLES.md` (add integration example) | + +> When you touch code that maps to a doc above, update that doc **in the same PR** — do not defer. diff --git a/references/git-workflow.md b/references/git-workflow.md new file mode 100644 index 0000000..12fc12c --- /dev/null +++ b/references/git-workflow.md @@ -0,0 +1,27 @@ +# Git Workflow + +## Branches + +- `master` is the main branch; branch off it and target it in PRs. +- Use descriptive prefixes seen in this repo: `chore/…`, `release/…`, and feature/fix branches. + +## Commits + +- No commitlint config is enforced. Write clear, imperative messages. +- **All commits must be signed** (per the PR checklist). + +## Pull Requests + +PRs use `.github/PULL_REQUEST_TEMPLATE.md`, which asks for: + +- **Changes** — what changed and why (classes/methods added, removed, deprecated, changed; public-API usage summary). +- **References** — support ticket, community/forum/StackOverflow links. +- **Testing** — how reviewers can test; checkboxes for unit and integration coverage. +- **Checklist** — read the Auth0 general contribution guidelines + Code of Conduct; all tests pass; all commits signed. + +CI on every PR (`build.yml`): restore → build (Release) → unit tests → integration tests → Codecov upload. Snyk and RL-Secure also run. + +## Changelog & Releases + +- `CHANGELOG.md` follows a Keep-a-Changelog-style layout. Changelog entries and version bumps are cut as part of the release flow (`ship`-cli, see `.shiprc`), not hand-edited during a feature PR. +- The version lives in three files kept in sync via `.shiprc`: `.version`, `build/common.props` (``), and `src/Auth0.AspNetCore.Authentication.Api/Version.cs` (`Version.Current`). Release CI: `.github/workflows/release.yml` and `nuget-release.yml`. diff --git a/references/pitfalls.md b/references/pitfalls.md new file mode 100644 index 0000000..2a53552 --- /dev/null +++ b/references/pitfalls.md @@ -0,0 +1,13 @@ +# Common Pitfalls + +1. **Domain format.** `options.Domain` must be `tenant.auth0.com` — **without** the `https://` scheme. The SDK constructs the Authority as `https://{Domain}` itself; passing a scheme produces a malformed Authority. + +2. **DPoP mode semantics.** `Allowed` (default) validates a DPoP proof *only if one is present* and still accepts Bearer tokens — this is the gradual-migration mode. `Required` rejects Bearer tokens entirely and demands a valid DPoP proof. `Disabled` is plain JWT Bearer. Confusing `Allowed` with `Required` is the most common behavioral surprise. + +3. **Event preservation.** When editing `AuthenticationBuilderExtensions.cs` or the DPoP event handlers, always preserve the consumer's existing `JwtBearerEvents` — the DPoP handlers *wrap* user events (run DPoP first, then delegate). Overwriting `Events.OnMessageReceived` etc. silently drops the consumer's handlers. + +4. **`InternalsVisibleTo`.** Tests reach internal validators via `` declared in the library `.csproj` (`…UnitTests`, `…IntegrationTests`, `DynamicProxyGenAssembly2` for Moq). If you move an internal type to a new assembly, update these. + +5. **Multi-targeting.** The library targets `net8.0;net10.0`. Package references are conditioned per TFM (e.g. `Microsoft.AspNetCore.Authentication.JwtBearer` 8.0.27 for net8.0, 10.0.3 for net10.0). Add framework-conditioned references when introducing a dependency, and don't assume APIs available only in net10.0. + +6. **DPoP timing.** `IatOffset` (default 300s) tolerates clock skew on the proof `iat`; `Leeway` (default 30s) applies to token lifetime checks. Tightening these can cause spurious `invalid_dpop_proof` failures under normal clock drift. diff --git a/references/testing.md b/references/testing.md new file mode 100644 index 0000000..65c039f --- /dev/null +++ b/references/testing.md @@ -0,0 +1,43 @@ +# Testing + +## Frameworks + +- **xUnit** — `[Fact]` for single cases, `[Theory]` + `[InlineData]` for parameterized. +- **Moq** — mock collaborators (e.g. `IDPoPProofValidationService` when testing event handlers). +- **FluentAssertions** — `result.Should().Contain(...)`, `act.Should().NotThrow()`. + +## Layout + +- `tests/Auth0.AspNetCore.Authentication.Api.UnitTests/` — unit tests, no network/credentials. This is the default suite. +- `tests/Auth0.AspNetCore.Authentication.Api.IntegrationTests/` — live-tenant tests (Ask First). + +Both test projects use `` (declared in the library `.csproj`) so tests can exercise internal validators directly. + +## Unit test conventions + +- Descriptive method names with underscores, e.g. `CreateAgentString_ReturnsBase64EncodedJson_With_Correct_Name_And_Version`. +- Test each DPoP validator independently against the internal methods in `DPoPProofValidationService`. +- Mock `IDPoPProofValidationService` when testing the event-handler chain (`MessageReceived`, `TokenValidated`, `Challenge`). + +## Integration / acceptance tests (Ask First) + +> ⚠️ These hit a live Auth0 tenant, are slow, may cost money, and obtain real tokens. Ask before running (see Boundaries). + +Requires these environment variables (provided as CI secrets in `.github/workflows/build.yml`): + +- `BASIC_DOMAIN`, `BASIC_AUDIENCE`, `BASIC_CLIENT_ID`, `BASIC_CLIENT_SECRET` +- `DPOP_ALLOWED_*` and `DPOP_REQUIRED_*` (domain, audience, client id/secret, dpop mode) +- `CUSTOM_DOMAIN_1_*` and `CUSTOM_DOMAIN_2_*` + +```bash +dotnet test tests/Auth0.AspNetCore.Authentication.Api.IntegrationTests/Auth0.AspNetCore.Authentication.Api.IntegrationTests.csproj +``` + +Patterns: +- `TestWebApplicationFactory` spins up a `TestServer`; `Auth0TokenHelper` obtains real client-credentials tokens; `DPoPHelper` generates DPoP proofs with real EC keys (`ECDsa.Create(ECCurve.NamedCurves.nistP256)`). +- Use the `Auth0Scenario` configuration to select Basic / DPoPAllowed / DPoPRequired / CustomDomains environments. +- **Never hardcode tokens** — always go through `Auth0TokenHelper`. + +## Coverage + +Coverlet collects coverage (`--collect:"XPlat Code coverage"`, Cobertura format) and CI uploads it to Codecov. No hard threshold is enforced in CI (`fail_ci_if_error: false`).