Add authentication
This commit is contained in:
159
.planning/phases/02-authentication-foundation/02-04-SUMMARY.md
Normal file
159
.planning/phases/02-authentication-foundation/02-04-SUMMARY.md
Normal file
@@ -0,0 +1,159 @@
|
||||
---
|
||||
phase: 02-authentication-foundation
|
||||
plan: 04
|
||||
subsystem: auth
|
||||
tags: [android, oidc, appauth, encryptedsharedpreferences, authstate]
|
||||
|
||||
requires:
|
||||
- phase: 02-authentication-foundation
|
||||
provides: 02-01 Phase 2 Android AppAuth/Security Crypto dependencies and OIDC constants
|
||||
- phase: 02-authentication-foundation
|
||||
provides: 02-03 common OidcClient, OidcResult, and SecureAuthStateStore expect contracts
|
||||
provides:
|
||||
- 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
|
||||
affects: [02-06-auth-session-ui, 02-07-auth-integration-verification]
|
||||
|
||||
tech-stack:
|
||||
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."
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.android.kt
|
||||
- composeApp/src/androidMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.android.kt
|
||||
modified:
|
||||
- composeApp/src/androidMain/AndroidManifest.xml
|
||||
|
||||
key-decisions:
|
||||
- "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."
|
||||
|
||||
patterns-established:
|
||||
- "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."
|
||||
|
||||
requirements-completed: [AUTH-01, AUTH-02, AUTH-04, AUTH-05]
|
||||
|
||||
duration: 8 min
|
||||
completed: 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*
|
||||
Reference in New Issue
Block a user