- Summarize iOS AppAuth and Keychain implementation - Record verification results and deviations
7.4 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 | 05 | auth |
|
|
|
|
|
|
|
|
|
27m | 2026-04-28 |
Phase 02 Plan 05: iOS AppAuth Actuals Summary
iOS AppAuth login, fresh-token refresh, RP-initiated logout, Keychain AuthState persistence, and recipe://callback forwarding behind the Phase 02 common auth contracts.
Performance
- Duration: 27 min
- Started: 2026-04-28T13:52:54Z
- Completed: 2026-04-28T14:19:03Z
- Tasks: 2
- Files modified: 5
Accomplishments
- Added the iOS
OidcClientactual using AppAuth discovery, authorization-code flow with PKCE, exactopenid profile email offline_accessscopes, fresh-token refresh, and end-session logout. - Added the iOS secure store actual using Keychain-backed settings with
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. - Registered the
recipeURL scheme and wired SwiftUI.onOpenURLto forward onlyrecipe://callbackURLs to the active AppAuth external user-agent session. - Added
iosApp/PodfilewithAppAuthso the iOS shell has explicit pod integration alongside the existing KMP CocoaPods block.
Task Commits
- Task 1: Implement iOS AppAuth OidcClient actual and CocoaPods bridge -
ac9fc61(feat) - Task 2: Implement iOS Keychain store and callback wiring -
88dc8d7(feat)
Files Created/Modified
composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt- AppAuth-iOS login, refresh, logout, AuthState archive/restore, and callback bridge.composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.ios.kt- Keychain-backed opaque AuthState store with the required accessibility class.iosApp/Podfile- iOS target Podfile declaring theAppAuthpod.iosApp/iosApp/Info.plist-CFBundleURLTypesregistration for therecipecustom URL scheme.iosApp/iosApp/iOSApp.swift- SwiftUI.onOpenURLforwarding forrecipe://callback.
Decisions Made
See frontmatter key-decisions.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] Implemented SecureAuthStateStore.ios.kt during Task 1
- Found during: Task 1 verification
- Issue:
./gradlew :composeApp:compileKotlinIosSimulatorArm64cannot pass after adding onlyOidcClient.ios.ktbecause the commonSecureAuthStateStoreexpect class also requires an iOS actual. - Fix: Added the Keychain-backed iOS secure store in the Task 1 commit, then Task 2 added the URL scheme and Swift callback wiring.
- Files modified:
composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.ios.kt - Verification:
./gradlew :composeApp:compileKotlinIosSimulatorArm64 - Committed in:
ac9fc61
2. [Rule 3 - Blocking] Wrapped AppAuth-iOS secure archive in JSON
- Found during: Task 1 implementation
- Issue: AppAuth-iOS 2.0.0 exposes
OIDAuthStateasNSSecureCoding; it does not expose the Android-styleserialize()/ JSON-deserialize API assumed by the plan. - Fix: Persisted a JSON wrapper containing the full
NSKeyedArchiversecure archive ofOIDAuthState, preserving the common opaqueauthStateJsoncontract while using AppAuth-iOS' supported persistence mechanism. - Files modified:
composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt - Verification:
./gradlew :composeApp:compileKotlinIosSimulatorArm64 - Committed in:
ac9fc61
Total deviations: 2 auto-fixed (2 x Rule 3). Impact on plan: No auth behavior was reduced. Both fixes were required for the iOS target to compile against the actual AppAuth-iOS API and the existing common expect contracts.
Known Stubs
None.
Threat Flags
None beyond the plan's threat model. This plan intentionally touches the browser callback, Keychain storage, and Swift-to-KMP callback trust boundaries already listed in 02-05-PLAN.md.
Issues Encountered
iosApp/Podfiledid not exist even though the plan listed it inread_first; it was created in Task 1.- A parallel
git addattempt briefly hit Git's index lock. Staging was retried sequentially; no repository state was lost. ./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64passed as an extra confidence check and confirmedIosAppAuthBridge.shared.resumeExternalUserAgentFlow(urlString:)is exported to Swift.
User Setup Required
None for this plan. Real login still requires the Authentik provider configuration documented in docs/authentik-setup.md.
Verification
- Task 1 acceptance greps - PASS
- Task 2 acceptance greps - PASS
./gradlew :composeApp:compileKotlinIosSimulatorArm64- PASS- Extra:
./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64- PASS - Token/logging scan - PASS; no
Logger,println, or token/AuthState logging calls were added.
Next Phase Readiness
Plan 02-06 can consume the common OidcClient and SecureAuthStateStore on iOS. Plan 02-07 should still run real iOS/Authenik UAT for browser handoff, refresh across relaunch, and end-session behavior.
Self-Check: PASSED
- Created/modified files exist: all five plan-owned source/config files plus this summary were found.
- Commits exist:
ac9fc61and88dc8d7were found in git history. - Acceptance criteria: all Task 1 and Task 2 grep checks passed.
- Plan-level verification:
./gradlew :composeApp:compileKotlinIosSimulatorArm64passed.
Phase: 02-authentication-foundation Completed: 2026-04-28