|
| 1 | +# Decision: WebApp OIDC Authentication (#44) |
| 2 | + |
| 3 | +**Date:** 2026-03-14 |
| 4 | +**Author:** Kaylee (Full-stack Dev) |
| 5 | +**Status:** IMPLEMENTED |
| 6 | +**Branch:** `feature/44-webapp-oidc` |
| 7 | + |
| 8 | +## Summary |
| 9 | + |
| 10 | +Added Microsoft.Identity.Web OIDC authentication to the Blazor WebApp with the same conditional `Auth:UseEntraId` pattern used in the API (Wash's #43 work). |
| 11 | + |
| 12 | +## What Changed |
| 13 | + |
| 14 | +### NuGet Packages Added |
| 15 | +- `Microsoft.Identity.Web` — OIDC/OpenID Connect integration |
| 16 | +- `Microsoft.Identity.Web.UI` — Sign-in/sign-out controller endpoints |
| 17 | +- `Microsoft.Identity.Web.DownstreamApi` — Token acquisition for API calls |
| 18 | +- `Aspire.StackExchange.Redis.DistributedCaching` (v13.3.0-preview.1.26156.1) — Redis-backed distributed token cache |
| 19 | + |
| 20 | +### Auth Flow (Conditional) |
| 21 | + |
| 22 | +When `Auth:UseEntraId` is **true**: |
| 23 | +- `AddMicrosoftIdentityWebApp()` configures OIDC with Entra ID |
| 24 | +- `EnableTokenAcquisitionToCallDownstreamApi()` acquires tokens for API |
| 25 | +- `AddDistributedTokenCaches()` + `AddRedisDistributedCache("cache")` for Redis-backed token cache |
| 26 | +- `AuthenticatedApiDelegatingHandler` attaches Bearer tokens to all outgoing HttpClient calls |
| 27 | +- `AddMicrosoftIdentityUI()` provides `/MicrosoftIdentity/Account/SignIn` and `SignOut` endpoints |
| 28 | + |
| 29 | +When `Auth:UseEntraId` is **false** (default): |
| 30 | +- Existing `DevAuthHandler` provides auto-authenticated dev user (no config needed) |
| 31 | + |
| 32 | +### New Files |
| 33 | +- `Auth/AuthenticatedApiDelegatingHandler.cs` — DelegatingHandler using ITokenAcquisition |
| 34 | +- `Components/Layout/LoginDisplay.razor` — Sign-in/sign-out UI with Bootstrap icons (bi-person, bi-box-arrow-right) |
| 35 | + |
| 36 | +### Modified Files |
| 37 | +- `Program.cs` — Conditional auth registration, HttpClient handler wiring, MapControllers |
| 38 | +- `SentenceStudio.WebApp.csproj` — NuGet packages |
| 39 | +- `Components/App.razor` — `<CascadingAuthenticationState>` wrapper |
| 40 | +- `Components/Layout/MainLayout.razor` — LoginDisplay in top-row |
| 41 | +- `Components/_Imports.razor` — `Microsoft.AspNetCore.Components.Authorization` using |
| 42 | +- `appsettings.json` (gitignored) — AzureAd, Auth, DownstreamApi sections |
| 43 | + |
| 44 | +### Configuration Required for Production |
| 45 | +1. Set `Auth:UseEntraId` to `true` |
| 46 | +2. Store client secret in user-secrets: `dotnet user-secrets set "AzureAd:ClientSecret" "<value>"` |
| 47 | +3. Redis must be running (Aspire AppHost already configures this) |
| 48 | + |
| 49 | +## Design Rationale |
| 50 | + |
| 51 | +- **Conditional pattern:** Matches API's DevAuthHandler approach — zero friction for local dev |
| 52 | +- **Redis token cache:** WebApp is server-rendered, needs shared token cache; Redis already in AppHost |
| 53 | +- **DelegatingHandler on all HttpClients:** `ConfigureHttpClientDefaults` ensures all API clients get auth tokens automatically |
| 54 | +- **No emojis:** Bootstrap icons only per team standards |
| 55 | + |
| 56 | +## Dependencies |
| 57 | +- Requires Entra ID app registration (#42) for production use |
| 58 | +- Works alongside API auth (#43) — same tenant/scopes |
| 59 | +- Redis resource in AppHost (already configured) |
0 commit comments