|
| 1 | +# Decision: JWT Bearer Authentication for API (#43) |
| 2 | + |
| 3 | +**Date:** 2026-03-13 |
| 4 | +**Author:** Wash (Backend Dev) |
| 5 | +**Status:** IMPLEMENTED |
| 6 | +**Branch:** `feature/43-api-jwt-bearer` |
| 7 | + |
| 8 | +## Context |
| 9 | + |
| 10 | +The API used only `DevAuthHandler` — a hardcoded auth handler that injects fake claims for local development. Issue #43 requires adding real JWT Bearer authentication via Microsoft Entra ID while keeping DevAuthHandler for local dev. |
| 11 | + |
| 12 | +## Decision |
| 13 | + |
| 14 | +### Conditional Authentication via Config Flag |
| 15 | + |
| 16 | +- `Auth:UseEntraId` (bool, default `false`) controls which auth scheme is active. |
| 17 | +- When `true`: Microsoft.Identity.Web validates JWT Bearer tokens against Entra ID. |
| 18 | +- When `false`: DevAuthHandler provides fake dev claims (existing behavior). |
| 19 | + |
| 20 | +### Scope-Based Authorization Policies |
| 21 | + |
| 22 | +Four policies defined matching the Entra ID app registration scopes: |
| 23 | +- `RequireUserRead` → `user.read` |
| 24 | +- `RequireUserWrite` → `user.write` |
| 25 | +- `RequireAiAccess` → `ai.access` |
| 26 | +- `RequireSyncReadWrite` → `sync.readwrite` |
| 27 | + |
| 28 | +Endpoints currently use `.RequireAuthorization()` (any authenticated user). Scope-based policies are available for endpoints to opt into as needed. |
| 29 | + |
| 30 | +### TenantContextMiddleware Dual Claim Mapping |
| 31 | + |
| 32 | +The middleware now checks both Entra ID claims (`tid`, `oid`, `name`, `preferred_username`) and DevAuthHandler claims (`tenant_id`, `NameIdentifier`, `Name`, `Email`). Entra ID claims take precedence. |
| 33 | + |
| 34 | +### AzureAd Configuration |
| 35 | + |
| 36 | +Public IDs (tenant, client, audience) are in `appsettings.Development.json` (tracked in git). The `appsettings.json` file is gitignored. AppHost also passes these as environment variables. |
| 37 | + |
| 38 | +## Files Changed |
| 39 | + |
| 40 | +| File | Change | |
| 41 | +|------|--------| |
| 42 | +| `SentenceStudio.Api.csproj` | Added `Microsoft.Identity.Web` v3.8.2 | |
| 43 | +| `Program.cs` | Conditional auth registration, scope policies | |
| 44 | +| `TenantContextMiddleware.cs` | Dual claim mapping (Entra ID + Dev) | |
| 45 | +| `appsettings.Development.json` | AzureAd section with public IDs | |
| 46 | +| `appsettings.json` | AzureAd section (local only, gitignored) | |
| 47 | +| `AppHost.cs` | AzureAd env vars passed to API service | |
| 48 | + |
| 49 | +## Consequences |
| 50 | + |
| 51 | +- **No breaking change** — defaults to DevAuthHandler (`UseEntraId: false`) |
| 52 | +- **Ready for production** — flip `Auth:UseEntraId` to `true` and tokens are validated |
| 53 | +- **Single-tenant** — `AzureADMyOrg` via Microsoft.Identity.Web defaults |
| 54 | +- **Next steps:** Apply scope policies to specific endpoints (#44+), add MAUI client MSAL (#45) |
0 commit comments