Files
recipe/.planning/phases/02-authentication-foundation/02-VALIDATION.md
2026-04-29 20:54:13 +02:00

6.1 KiB

phase, slug, status, nyquist_compliant, wave_0_complete, created
phase slug status nyquist_compliant wave_0_complete created
02 authentication-foundation draft false false 2026-04-27

Phase 02 — Validation Strategy

Per-phase validation contract for feedback sampling during execution.


Test Infrastructure

Property Value
Framework kotlin.test + JUnit for server; KMP common tests for auth state/store seams
Config file Existing Gradle/KMP test setup; no standalone test config
Quick run command ./gradlew :server:test :composeApp:jvmTest :shared:jvmTest
Full suite command ./gradlew check
Estimated runtime ~120 seconds

Sampling Rate

  • After every task commit: Run ./gradlew :server:test :composeApp:jvmTest :shared:jvmTest
  • After every plan wave: Run ./gradlew check
  • Before $gsd-verify-work: Full suite must be green and manual iOS Authentik login/logout UAT must be recorded
  • Max feedback latency: 120 seconds for quick checks

Per-Task Verification Map

Task ID Plan Wave Requirement Threat Ref Secure Behavior Test Type Automated Command File Exists Status
02-01-01 01 1 AUTH-01 T-02-01 OIDC config pins issuer, client ID, redirect URI, scopes, PKCE-compatible public-client flow build/unit ./gradlew :composeApp:compileKotlinIosSimulatorArm64 :composeApp:compileDebugKotlinAndroid W0 pending
02-01-02 01 1 AUTH-02 T-02-02 AuthState JSON store reads/writes/clears without using no-arg insecure Settings for secrets common unit + grep ./gradlew :composeApp:jvmTest W0 pending
02-02-01 02 1 AUTH-03 T-02-03 /api/v1/me rejects missing, expired, wrong-audience, and wrong-issuer tokens server integration ./gradlew :server:test --tests "*Auth*" W0 pending
02-02-02 02 1 AUTH-06 T-02-04 First authenticated /api/v1/me creates or updates a users row keyed by OIDC sub server integration ./gradlew :server:test --tests "*Me*" W0 pending
02-03-01 03 1 AUTH-04 T-02-05 Restored AuthState refreshes before /api/v1/me and transitions to authenticated without UI prompt common/platform-stub unit ./gradlew :composeApp:jvmTest W0 pending
02-03-02 03 1 AUTH-05 T-02-06 Logout calls end-session when possible and clears local AuthState even if network logout fails unit + manual iOS UAT ./gradlew :composeApp:jvmTest W0 pending

Status: pending · green · red · ⚠️ flaky


Wave 0 Requirements

  • server/src/test/kotlin/dev/ulfrx/recipe/auth/AuthJwtTest.kt — covers valid, missing, expired, wrong-audience, and wrong-issuer JWT cases for AUTH-03
  • server/src/test/kotlin/dev/ulfrx/recipe/auth/MeRouteTest.kt — covers JIT provisioning and /api/v1/me response shape for AUTH-06
  • composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/auth/AuthSessionTest.kt — covers login, restored session refresh, invalid-grant transition, and logout state transitions for AUTH-01, AUTH-04, AUTH-05
  • composeApp/src/commonTest/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStoreTest.kt — covers read/write/clear store contract and guards against insecure no-arg Settings() use for AUTH-02
  • docs/authentik-setup.md — includes manual iOS UAT checklist for fresh login, reopen-with-refresh, logout, and /api/v1/me

Manual-Only Verifications

Behavior Requirement Why Manual Test Instructions
Fresh iOS install opens Authentik, completes hosted login, and returns through recipe://callback AUTH-01 Requires real Authentik provider, iOS browser handoff, and custom URL callback Install on iOS simulator/device, tap Zaloguj się przez Authentik, authenticate, verify app shows Witaj, {displayName}!
Reopen after access token expiry remains signed in via refresh token AUTH-04 Depends on Authentik-issued refresh token and persisted OS secure storage Sign in, close app, wait or force short token lifetime in Authentik, reopen, verify app returns to authenticated screen without credential entry
Wyloguj się clears local tokens and invokes Authentik end-session AUTH-05 Requires browser/end-session behavior that unit tests can only stub Tap Wyloguj się, verify login screen appears, then relaunch and confirm no silent local session restore

Security Threat References

Threat Ref Threat Required Mitigation
T-02-01 Authorization-code interception through custom URL scheme Public client, PKCE S256, AppAuth state/nonce handling, redirect URI byte-match
T-02-02 Refresh token persisted in plaintext Explicit secure platform store; iOS Keychain and Android secure storage; no no-arg Settings() for auth secrets
T-02-03 Wrong-audience or wrong-issuer token accepted by server withIssuer, withAudience, 30-second leeway only, non-empty sub, negative JWT tests
T-02-04 Duplicate or stale user rows on concurrent first login Atomic upsert by unique sub; update email/display name on conflict
T-02-05 Token expiry breaks reopened sessions AppAuth performActionWithFreshTokens before authenticated calls plus Ktor bearer 401 refresh fallback
T-02-06 Logout leaves recoverable local refresh token Always clear persisted AuthState after logout attempt, even if end-session fails

Validation Sign-Off

  • All tasks have <automated> verify or Wave 0 dependencies represented in Phase 2 plans
  • Sampling continuity: no 3 consecutive tasks without automated verify
  • Wave 0 missing references are mapped into Phase 2 plan tasks
  • No watch-mode flags
  • Feedback latency target < 120s for quick checks is documented
  • nyquist_compliant: true set in frontmatter

Approval: plan-ready 2026-04-27; execution must keep nyquist_compliant: false and wave_0_complete: false until Wave 0 tests/manual-UAT artifacts actually exist.