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

8.7 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
02-authentication-foundation 04 auth
android
oidc
appauth
encryptedsharedpreferences
authstate
phase provides
02-authentication-foundation 02-01 Phase 2 Android AppAuth/Security Crypto dependencies and OIDC constants
phase provides
02-authentication-foundation 02-03 common OidcClient, OidcResult, and SecureAuthStateStore expect contracts
Android AppAuth authorization-code + PKCE login through the system browser
Android AppAuth AuthState JSON serialization for login and fresh-token refresh
Android RP-initiated logout through AppAuth EndSessionRequest when discovery metadata exposes end-session
Android EncryptedSharedPreferences-backed SecureAuthStateStore for opaque AuthState JSON
Android manifest registration for recipe://callback via RedirectUriReceiverActivity
02-06-auth-session-ui
02-07-auth-integration-verification
added patterns
Android OIDC actual resolves Context from Koin's Android context while preserving the no-arg common expect constructor.
AppAuth callback bridge uses private dynamic broadcast PendingIntents and suspendCancellableCoroutine.
AuthState JSON remains opaque; storage and refresh paths never log token-bearing values.
created modified
composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.android.kt
composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.android.kt
composeApp/src/androidMain/AndroidManifest.xml
Use Koin's registered Android Context from the no-arg Android actuals instead of changing common constructor contracts from 02-03.
Task 1 included the Android SecureAuthStateStore actual because Android target compilation cannot pass with only one of the auth expect actuals present.
Treat missing access tokens from token exchange/refresh as AuthError, not Success with an empty token.
Android AppAuth login/request scopes are pinned exactly to openid profile email offline_access.
Android token persistence is contained behind SecureAuthStateStore so the deprecated AndroidX Security Crypto implementation can be replaced later without touching AuthSession.
AUTH-01
AUTH-02
AUTH-04
AUTH-05
8 min 2026-04-28

Phase 02 Plan 04: Android OIDC Actuals Summary

Android AppAuth login, refresh, logout, and encrypted AuthState persistence wired behind the Phase 2 common auth contracts.

Performance

  • Duration: 8 min
  • Started: 2026-04-28T13:52:41Z
  • Completed: 2026-04-28T14:00:47Z
  • Tasks: 2
  • Files modified: 3

Accomplishments

  • Added Android OidcClient actual using AppAuth discovery, authorization-code flow, exact openid profile email offline_access scopes, token exchange, AuthState.jsonSerializeString(), AuthState.jsonDeserialize(...), and performActionWithFreshTokens.
  • Added Android SecureAuthStateStore actual backed by EncryptedSharedPreferences.
  • Registered net.openid.appauth.RedirectUriReceiverActivity for recipe://callback in the Android manifest.
  • Kept auth diagnostics token-safe: no logging of AuthState JSON, access tokens, refresh tokens, ID tokens, bearer headers, or authorization headers.

Task Commits

  1. Task 1: Implement Android AppAuth OidcClient actual - fa78ee3 (feat)
  2. Task 2: Implement Android secure AuthState store and callback manifest - 6385453 (feat)
  3. Rule 1 fix: Harden Android OIDC token result mapping - 11a5eeb (fix)

Files Created/Modified

  • composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.android.kt - Android AppAuth actual for login, refresh, logout, AuthState JSON serialization/deserialization, and OidcResult mapping.
  • composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.android.kt - Android encrypted storage actual for one opaque AuthState JSON blob per install.
  • composeApp/src/androidMain/AndroidManifest.xml - Explicit AppAuth redirect receiver registration for recipe://callback.

Decisions Made

See frontmatter key-decisions.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Added Android SecureAuthStateStore actual during Task 1

  • Found during: Task 1 verification
  • Issue: ./gradlew :composeApp:compileDebugKotlinAndroid failed because SecureAuthStateStore had a common expect declaration but no Android actual. The plan listed the store in Task 2, but the Android target cannot compile any auth expect declarations until both Android actuals exist.
  • Fix: Implemented SecureAuthStateStore.android.kt with EncryptedSharedPreferences during Task 1 so the required Task 1 Android compile gate could pass.
  • Files modified: composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.android.kt
  • Verification: ./gradlew :composeApp:compileDebugKotlinAndroid
  • Committed in: fa78ee3

2. [Rule 1 - Bug] Hardened token result mapping

  • Found during: Final correctness pass
  • Issue: The initial token exchange path could report success if AppAuth returned a TokenResponse with a missing access token.
  • Fix: Added explicit missing-token guards and preserved AppAuth discovery exceptions so network failures and auth failures map cleanly.
  • Files modified: composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.android.kt
  • Verification: ./gradlew :composeApp:compileDebugKotlinAndroid; all Task 1 and Task 2 grep gates re-run.
  • Committed in: 11a5eeb

Total deviations: 2 auto-fixed (1 x Rule 3 blocking, 1 x Rule 1 bug). Impact on plan: No scope expansion beyond Android auth ownership. The Rule 3 change only corrected task ordering required by Kotlin expect/actual compilation.

Known Stubs

None.

Threat Flags

None - all new trust-boundary surfaces were already listed in the plan threat model.

Issues Encountered

  • Concurrent iOS plan work appeared as untracked composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/ and iosApp/Podfile. These files were not read, staged, modified, or committed by this plan.
  • Pre-existing untracked .claude/ and AGENTS.md were left untouched.
  • STATE.md/ROADMAP.md updates were intentionally not performed by this spawned Android executor because the user constrained writes to Android-owned files plus this summary; central planning state remains orchestrator-owned.

User Setup Required

None.

Verification

  • ./gradlew :composeApp:compileDebugKotlinAndroid - PASS
  • grep -q 'AuthorizationServiceConfiguration.fetchFromIssuer' composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.android.kt - PASS
  • grep -q 'setScopes("openid", "profile", "email", "offline_access")' composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.android.kt - PASS
  • grep -q 'suspendCancellableCoroutine' composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.android.kt - PASS
  • grep -q 'performActionWithFreshTokens' composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.android.kt - PASS
  • grep -q 'EndSessionRequest' composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.android.kt - PASS
  • grep -q 'EncryptedSharedPreferences' composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.android.kt - PASS
  • ! grep -R 'Settings()' composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth - PASS
  • ! grep -R 'getSharedPreferences' composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.android.kt - PASS
  • grep -q 'RedirectUriReceiverActivity' composeApp/src/androidMain/AndroidManifest.xml - PASS
  • grep -q 'android:scheme="recipe"' composeApp/src/androidMain/AndroidManifest.xml - PASS
  • grep -q 'android:host="callback"' composeApp/src/androidMain/AndroidManifest.xml - PASS
  • Token/log scan for Logger, println, printStackTrace, Authorization:, and Bearer under Android auth files - PASS

Next Phase Readiness

Android auth actuals now compile behind the common contracts. AuthSession/UI integration in 02-06 can call login, refresh, logout, and the secure store without Android-specific APIs.

Self-Check: PASSED

  • Created files exist: OidcClient.android.kt, SecureAuthStateStore.android.kt, and this summary were found.
  • Modified files exist: AndroidManifest.xml contains RedirectUriReceiverActivity, android:scheme="recipe", and android:host="callback".
  • Commits exist: fa78ee3, 6385453, and 11a5eeb were found in git history.
  • Acceptance criteria: all required grep checks passed.
  • Plan-level verification: ./gradlew :composeApp:compileDebugKotlinAndroid passed.

Phase: 02-authentication-foundation Completed: 2026-04-28