# Code Review — Batch 05 cycle 3 (AZ-494) **Reviewer**: autodev in-band **Scope**: AZ-494 (JWT iss/aud validation) — implementation, tests, config, docs **Verdict**: **PASS_WITH_WARNINGS** — 0 Critical, 0 High, 0 Medium, 1 Low (cross-repo doc), 1 acknowledged operational gate by user decision. ## 1. Spec compliance All ACs except AC-7 are addressed. AC-7 (suite-level contract) requires writing to a file in the parent monorepo (`suite/_docs/10_auth.md`), which sits outside this workspace and outside autodev's blast radius. The cycle-2 deploy report has been updated to document the cross-repo obligation so it doesn't get lost. The user's Option B selection is honoured exactly: - `appsettings.json` ships with empty `Jwt.Issuer` / `Jwt.Audience` → production deploy without env vars fails at startup. - `appsettings.Development.json` ships with `DEV-ONLY-`-prefixed placeholders → local + Docker-compose flows work without touching `.env`. - The env-var precedence + fail-fast pattern is identical to the existing `JWT_SECRET` flow from AZ-487, so there's no second mental model for operators to learn. ## 2. Code quality | Area | Observation | Verdict | |---|---|---| | Single source of truth | `JwtTokenFactory.Create` remains the only `new JwtSecurityToken(...)` call site in the source tree (verified by grep). The new iss/aud params slot in alongside the existing `secret` / `subject` / `lifetime` / `extraClaims` / `algorithm` parameters — no parallel code path was introduced. | ✓ | | Helper layering | `JwtTestHelpers.MintAuthenticated` / `MintExpired` are thin wrappers (≤ 12 lines each) that resolve iss/aud from env and delegate to the factory. They live in `SatelliteProvider.IntegrationTests` because they read env vars — exactly the runner-side concern documented in `module-layout.md`. | ✓ | | Fail-fast extraction | The third copy of the "resolve required value, throw with a humanised message" pattern was extracted into a single `ResolveRequiredOrThrow` helper. `ResolveSecretOrThrow` remains separate because it has the additional ≥ 32-byte size check from AZ-487. This is acceptable: two distinct invariants. | ✓ | | Test mutual-isolation | Each new `[Fact]` saves and restores both `JWT_ISSUER` and `JWT_AUDIENCE` via `try/finally`, mirroring the existing pattern for `JWT_SECRET`. No parallel-test interference risk. | ✓ | | Surface area minimisation | `JwtTokenFactory.Create` gained two optional, nullable parameters at the END of the signature with defaults of `null`. Old callers behave identically. | ✓ | | Error message clarity | All three fail-fast messages name (a) the env var, (b) the config key, (c) the AZ-494 spec, and use the human label ("JWT issuer", "JWT audience"). | ✓ | | Logging | No new debug/trace logs added — only the existing startup-time `InvalidOperationException` path. This matches `coderule.mdc`. | ✓ | | Naming | `MintAuthenticated` / `MintExpired` / `ResolveIssuerOrThrow` / `ResolveAudienceOrThrow` all describe caller intent precisely. No vague "data"/"item"/"candidate" names. | ✓ | ## 3. Security | Aspect | Status | |---|---| | F-AUTH-2 (iss/aud not validated) | **Resolved** — `ValidateIssuer = true` + `ValidateAudience = true`, both wired to real values | | Token revocation list | Still absent — listed as a new Low finding in `owasp_review.md` A07 | | Secret leakage | No iss/aud value is logged at WARN / ERROR / INFO. Only the startup banner (Console.Out) prints them — intentional, for diagnosis. | ✓ | | Production safety | `appsettings.json` empty → process won't start without explicit env vars. Cannot accidentally ship a hard-coded prod issuer. | ✓ | | Test/prod isolation | `DEV-ONLY-` prefix means a grep can surface placeholder values at any time. | ✓ | ## 4. Performance No measurable impact. `TokenValidationParameters` now checks two additional string comparisons per request. Sub-microsecond per request — far below the 1 ms AZ-487 NFR budget. PT-07 / PT-08 from AZ-492 will pick up any aggregate regression at the next perf run. ## 5. Cross-task consistency - AZ-487 (JWT base layer) — extended, not replaced. The `RequireSignedTokens` / `RequireExpirationTime` / `ClockSkew = 30 s` / secret ≥ 32 bytes invariants all remain. - AZ-491 (consolidate JWT test helpers) — the new `MintAuthenticated` / `MintExpired` wrappers strengthen the AZ-491 contract (delegate to the canonical factory; no parallel mint logic). - AZ-492 (perf harness) — `PerfBootstrap.MintToken` was updated in the same way as integration call sites. Without this update the perf harness would have started emitting 401s after AZ-494 landed. - AZ-493 (DB reset hook) — unaffected; runs against the same env. - AZ-495 (doc folder convention) — unaffected. - AZ-496 (bump aspnetcore 8.0.25) — directly relevant: AZ-494's `ValidateIssuer` / `ValidateAudience` semantics are unchanged across `Microsoft.AspNetCore.Authentication.JwtBearer` 8.0.21 → 8.0.25. ## 6. Findings ### Low **L1 — Cross-repo doc deferred (AC-7)** - Where: `suite/_docs/10_auth.md` (outside this workspace). - Why noted: AC-7 explicitly calls for an update there; the autodev process treats this as out-of-scope per the workspace-boundary rule. - Disposition: documented in `deploy_cycle2.md`'s R3 follow-up section. ### Acknowledged operational gate (NOT a finding) The Option B contract is **deliberately** that production deploys without real iss/aud values fail at startup. That's the user-selected forcing function for admin-team confirmation. Not a code defect. ## 7. AC-by-AC re-check | AC | Verification | Verdict | |---|---|---| | AC-1 | `JwtIntegrationTests.WrongIssuer_Returns401` asserts 401 against `https://wrong-issuer.invalid/` | ✓ | | AC-2 | `JwtIntegrationTests.WrongAudience_Returns401` asserts 401 against `wrong-audience-not-satellite` | ✓ | | AC-3 | `JwtIntegrationTests.ValidToken_Returns200_OnHealthyEndpoint` mints through `MintAuthenticated` (env-sourced iss/aud) | ✓ | | AC-4 | 4 fail-fast unit tests + manual smoke ("docker compose up without env vars") path documented | ✓ | | AC-5 | All integration test call sites migrated to `MintAuthenticated`; verified via grep | ✓ | | AC-6 | `security_report.md` + `owasp_review.md` updated | ✓ | | AC-7 | Cross-repo write deferred; documented | ◐ deferred | ## 8. Verdict **PASS_WITH_WARNINGS** — proceed to ship. The single Low finding is structurally outside this workspace.