Files
satellite-provider/_docs/03_implementation/batch_05_cycle3_report.md
T
Oleksandr Bezdieniezhnykh f979e18811 [AZ-494] Enable JWT iss/aud validation with fail-fast startup
Option B per user decision: production ships with empty Jwt.Issuer /
Jwt.Audience in appsettings.json so the API process refuses to start
unless JWT_ISSUER + JWT_AUDIENCE env vars are supplied. Development
ships with grep-friendly DEV-ONLY- placeholders so local + docker
flows keep working unchanged.

AuthenticationServiceCollectionExtensions flips ValidateIssuer +
ValidateAudience to true and wires ValidIssuer / ValidAudience via a
new ResolveRequiredOrThrow helper that all three required values
(secret, iss, aud) now share. JwtTokenFactory.Create + CreateExpired
gain optional iss / aud parameters (default null) so existing call
sites compile unchanged. JwtTestHelpers adds MintAuthenticated /
MintExpired wrappers that resolve iss + aud from env, plus
ResolveIssuerOrThrow / ResolveAudienceOrThrow. PerfBootstrap.MintToken
+ Program.cs JWT bootstrap migrated to the new surface so the perf
harness and the integration runner both validate against the same
contract.

Adds 4 fail-fast unit tests (missing/empty issuer + audience), 2
negative integration scenarios (WrongIssuer_Returns401,
WrongAudience_Returns401), and re-tags every existing integration
mint site via MintAuthenticated.

Compose, .env.example, run-tests.sh, run-performance-tests.sh all
load + export JWT_ISSUER + JWT_AUDIENCE alongside JWT_SECRET.

Resolves F-AUTH-2 (security_report.md + owasp_review.md). AC-7
(cross-repo suite/_docs/10_auth.md write) deferred — outside this
workspace; tracked in deploy_cycle2.md R3 follow-up.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 02:28:48 +03:00

11 KiB

Batch Report — Batch 05 cycle 3

Batch: 05 (cycle 3) Tasks: AZ-494 (JWT iss/aud validation — enable + configure) Date: 2026-05-12

Task Results

Task Status Files Modified Tests AC Coverage Issues
AZ-494_jwt_iss_aud_validation Done (Option B) 1 prod source + 1 TestSupport + 4 IntegrationTests + 1 unit test + 4 config + 4 scripts/compose + 6 docs 4 new unit tests + 2 new integration scenarios; existing 13 unit + 8 integration cases re-tagged to mint via env iss/aud 6/7 ACs addressed; AC-7 deferred (cross-repo suite/_docs/10_auth.md write) 0 blockers; 1 Low (cross-repo doc), 1 acknowledged operational gate (admin team must supply real prod iss/aud — fail-fast at deploy enforces this).

AC Test Coverage: 6 of 7 addressed (AC-7 deferred — cross-repo)

Code Review Verdict: pending (this batch report precedes per-batch review)

Auto-Fix Attempts: 0

Stuck Agents: None

What was implemented

The task spec offered three options for handling the blocker (admin team has not yet confirmed production iss / aud values). The user selected Option B: implement the full validation plumbing now with clearly-tagged DEV-only values in appsettings.Development.json so local tests work, but leave appsettings.json empty so production deploys without explicit JWT_ISSUER / JWT_AUDIENCE environment variables fail at startup, not at runtime.

The implementation therefore:

  1. Enables ValidateIssuer = true and ValidateAudience = true in the production token-validation pipeline.
  2. Sources both values from JWT_ISSUER / JWT_AUDIENCE env vars with Jwt:Issuer / Jwt:Audience config keys as fallback (same resolution pattern as JWT_SECRET from AZ-487).
  3. Throws InvalidOperationException at startup if either value is missing or whitespace — the message names the env var, the config key, and the AZ-494 task spec.
  4. Threads iss / aud through the canonical SatelliteProvider.TestSupport.JwtTokenFactory.Create surface (the post-AZ-491 single source of truth) so every existing test path continues to mint matching tokens.
  5. Adds a thin convenience layer in SatelliteProvider.IntegrationTests/JwtTestHelpers (MintAuthenticated, MintExpired, ResolveIssuerOrThrow, ResolveAudienceOrThrow) so integration test call sites stay terse and centrally fail-fast on missing env vars.
  6. Adds two new negative integration tests (WrongIssuer_Returns401, WrongAudience_Returns401) and four new unit fail-fast tests (AddSatelliteJwt_ThrowsOnMissingIssuer / _ThrowsOnEmptyIssuer / _ThrowsOnMissingAudience / _ThrowsOnEmptyAudience).
  7. Updates security artefacts (security_report.md flips F-AUTH-2 to RESOLVED, owasp_review.md A07 reflects same), the architecture + module docs (architecture.md, modules/api_program.md, modules/tests_integration.md, modules/tests_unit.md), the cycle-2 deploy report (R3 follow-up note), and the traceability matrix (5 new rows for AZ-494 AC-1..AC-7).

Added

  • SatelliteProvider.IntegrationTests/JwtTestHelpers.cs — three new public helpers:
    • ResolveIssuerOrThrow() / ResolveAudienceOrThrow() — mirror the existing ResolveSecretOrThrow pattern (read env, throw InvalidOperationException with a humanised message if missing).
    • MintAuthenticated(...) — convenience wrapper: defaults issuer + audience to the env-resolved values, accepts explicit overrides for negative test cases.
    • MintExpired(...) — convenience wrapper for the existing JwtTokenFactory.CreateExpired overload, same env-resolution behaviour.
  • 4 unit tests in SatelliteProvider.Tests/Authentication/AuthenticationServiceCollectionExtensionsTests.cs: AddSatelliteJwt_ThrowsOnMissingIssuer, AddSatelliteJwt_ThrowsOnEmptyIssuer, AddSatelliteJwt_ThrowsOnMissingAudience, AddSatelliteJwt_ThrowsOnEmptyAudience.
  • 2 integration tests in SatelliteProvider.IntegrationTests/JwtIntegrationTests.cs: WrongIssuer_Returns401, WrongAudience_Returns401.

Modified

  • SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs — added JwtIssuerEnvVar / JwtIssuerConfigKey / JwtAudienceEnvVar / JwtAudienceConfigKey constants; flipped ValidateIssuer / ValidateAudience to true and wired ValidIssuer / ValidAudience; extracted a single ResolveRequiredOrThrow helper that all three required values now flow through.
  • SatelliteProvider.TestSupport/JwtTokenFactory.csCreate(...) and CreateExpired(...) gained optional issuer / audience parameters defaulted to null (old call sites still produce identical tokens; new call sites pass real values).
  • SatelliteProvider.IntegrationTests/PerfBootstrap.csMintToken() now also resolves iss + aud and passes them through to JwtTokenFactory.Create. Without this the perf harness's bearer token would fail validation against the AZ-494-hardened API.
  • SatelliteProvider.IntegrationTests/Program.cs — JWT bootstrap now resolves all three required values (secret + iss + aud) inside a single try/catch and prints them at startup. The MintAuthenticated helper replaces the inline JwtTokenFactory.Create call that used to live in Main.
  • SatelliteProvider.IntegrationTests/JwtIntegrationTests.csAnonymousRequest_*, ExpiredToken_Returns401, InvalidSignature_Returns401, ValidToken_Returns200_OnHealthyEndpoint all migrated to JwtTestHelpers.MintAuthenticated / JwtTestHelpers.MintExpired. Adds the two new scenarios to RunAll.
  • SatelliteProvider.IntegrationTests/UavUploadTests.cs — every JwtTokenFactory.Create(...) call replaced with JwtTestHelpers.MintAuthenticated(...). Direct using SatelliteProvider.TestSupport; dropped (no longer needed at this seam).
  • SatelliteProvider.Tests/Authentication/AuthenticationServiceCollectionExtensionsTests.cs — existing _ConfiguresTokenValidationParameters_AsPerContract and _PrefersEnvironmentVariableOverConfiguration cases updated to assert the AZ-494 contract; BuildValidConfiguration() now seeds iss + aud; static [Fact] setup/teardown saves and restores both env vars.
  • SatelliteProvider.Api/appsettings.json — added empty Jwt.Issuer and Jwt.Audience keys (the fail-fast contract requires env-var or non-empty config; empty here forces ops to supply env vars in prod).
  • SatelliteProvider.Api/appsettings.Development.json — placeholder dev values prefixed DEV-ONLY- so a grep for that prefix surfaces every "remember to replace" site.
  • .env.example — documents JWT_ISSUER and JWT_AUDIENCE with the fail-fast contract and a one-line example value pair (same DEV-ONLY- prefix).
  • docker-compose.yml / docker-compose.tests.ymlJWT_ISSUER and JWT_AUDIENCE now passed through to both the api and integration-tests services.
  • scripts/run-tests.sh / scripts/run-performance-tests.sh.env load + fail-fast checks for both new vars mirror the existing JWT_SECRET flow; both are exported so Docker Compose and the perf bootstrap see them.

Documentation

  • _docs/02_document/architecture.md — token contract bullet + Security Architecture authentication paragraph updated.
  • _docs/02_document/modules/api_program.md — JWT authentication section + Configuration section.
  • _docs/02_document/modules/tests_integration.md — env-var prerequisites updated; JwtIntegrationTests and JwtTestHelpers entries describe the new AZ-494 surface.
  • _docs/02_document/modules/tests_unit.mdAuthenticationServiceCollectionExtensionsTests entry now lists the four AZ-494 fail-fast cases plus the updated config-precedence and contract assertions.
  • _docs/02_document/tests/traceability-matrix.md — 5 new rows for AC-1..AC-7 (AC-7 marked deferred); the AZ-487 NFR rows updated to acknowledge the AZ-494 extension.
  • _docs/03_implementation/deploy_cycle2.md — R3 follow-up note marked RESOLVED in cycle 3 (AZ-494) with the residual operational gate spelled out.
  • _docs/05_security/security_report.md — F-AUTH-2 flipped to RESOLVED cycle 3 (AZ-494); verdict reconciliation + recommendations updated.
  • _docs/05_security/owasp_review.md — A07 row updated; new Low finding for residual "no token revocation list" gap noted as a separate follow-up.

AC verification

AC Description Verification
AC-1 Wrong iss token returns 401 JwtIntegrationTests.WrongIssuer_Returns401 (integration; runtime gate at Step 16)
AC-2 Wrong aud token returns 401 JwtIntegrationTests.WrongAudience_Returns401 (integration; runtime gate at Step 16)
AC-3 Matching iss + aud accepted JwtIntegrationTests.ValidToken_Returns200_OnHealthyEndpoint retains its assertion; tokens now minted via env-resolved iss/aud through MintAuthenticated
AC-4 Missing config fails fast 4 new unit tests in AuthenticationServiceCollectionExtensionsTests; manual docker compose up without env vars throws InvalidOperationException per the contract
AC-5 Existing tests pass with matched fixtures All JwtTokenFactory.Create direct call sites in the integration project removed in favour of MintAuthenticated (verified via Grep); unit suite still mints via the factory with explicit iss/aud
AC-6 Security artefacts updated security_report.md + owasp_review.md updated this batch
AC-7 Suite-level contract reflects validation Deferredsuite/_docs/10_auth.md lives in the parent monorepo, outside this workspace. Cross-repo write is out of scope for satellite-provider's autodev. deploy_cycle2.md notes the cross-repo obligation.

Static / process checks

  • dotnet format whitespace --verify-no-changes will run as part of scripts/run-tests.sh at Step 16.
  • ReadLints on every modified C# file returned 0 warnings.
  • Repo-wide grep for JwtTokenFactory.Create confirms only SatelliteProvider.Tests (unit, which intentionally exercises the factory directly with explicit iss/aud) + PerfBootstrap.MintToken + JwtTestHelpers.MintAuthenticated / MintExpired call it now — the integration suite never bypasses the env-resolution wrapper.
  • .env.example keeps the DEV-ONLY- prefix grep-friendly so a future ops review can surface every placeholder site at once.

Risks & follow-ups

  • Operational gate (intentional, by Option B) — production deploy WITHOUT JWT_ISSUER + JWT_AUDIENCE env vars will fail at process start with the InvalidOperationException message documented above. This is the controlled deploy-time forcing function for admin-team confirmation.
  • Cross-repo doc (AC-7) — suite/_docs/10_auth.md write deferred. Will surface as a _docs/_process_leftovers/ entry if the suite repo still needs the update after this autodev finishes.