Add authentication
This commit is contained in:
153
.planning/phases/02-authentication-foundation/02-05-SUMMARY.md
Normal file
153
.planning/phases/02-authentication-foundation/02-05-SUMMARY.md
Normal file
@@ -0,0 +1,153 @@
|
||||
---
|
||||
phase: 02-authentication-foundation
|
||||
plan: 05
|
||||
subsystem: auth
|
||||
tags: [oidc, appauth, ios, keychain, swiftui, callback]
|
||||
|
||||
requires:
|
||||
- phase: 02-authentication-foundation
|
||||
provides: 02-01 CocoaPods/AppAuth dependency wiring and OIDC constants
|
||||
- phase: 02-authentication-foundation
|
||||
provides: 02-03 common OidcClient and SecureAuthStateStore contracts
|
||||
provides:
|
||||
- iOS AppAuth OidcClient actual with login, refresh, logout, and callback bridge
|
||||
- iOS Keychain-backed SecureAuthStateStore using kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
||||
- recipe URL scheme registration in Info.plist
|
||||
- SwiftUI onOpenURL callback forwarding into the current AppAuth flow
|
||||
- iosApp Podfile with AppAuth pod integration
|
||||
affects: [02-06-auth-session-ui, 02-07-auth-integration-verification]
|
||||
|
||||
tech-stack:
|
||||
added:
|
||||
- AppAuth CocoaPod reference in iosApp/Podfile
|
||||
patterns:
|
||||
- "iOS AppAuth bridge: Kotlin singleton holds currentAuthorizationFlow; SwiftUI forwards recipe://callback URLs by absolute string."
|
||||
- "iOS AuthState persistence: full OIDAuthState NSSecureCoding archive wrapped in an opaque JSON string and stored through KeychainSettings."
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt
|
||||
- composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.ios.kt
|
||||
- iosApp/Podfile
|
||||
modified:
|
||||
- iosApp/iosApp/Info.plist
|
||||
- iosApp/iosApp/iOSApp.swift
|
||||
|
||||
key-decisions:
|
||||
- "AppAuth-iOS AuthState persistence uses NSSecureCoding wrapped in JSON because AppAuth-iOS 2.0.0 does not expose the Android-style serialize()/jsonDeserialize API."
|
||||
- "SecureAuthStateStore was implemented in the first task commit because the Task 1 compile gate cannot pass while the common expect class lacks an iOS actual."
|
||||
- "SwiftUI forwards only recipe://callback URLs to the KMP bridge; other URLs are ignored before AppAuth sees them."
|
||||
|
||||
patterns-established:
|
||||
- "Never log token-bearing values in iOS auth actuals; token variables are only returned through OidcResult or stored in Keychain."
|
||||
- "Mobile callback state remains inside AppAuth's current external user-agent session and is consumed once."
|
||||
|
||||
requirements-completed: [AUTH-01, AUTH-02, AUTH-04, AUTH-05]
|
||||
|
||||
duration: 27m
|
||||
completed: 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 `OidcClient` actual using AppAuth discovery, authorization-code flow with PKCE, exact `openid profile email offline_access` scopes, fresh-token refresh, and end-session logout.
|
||||
- Added the iOS secure store actual using Keychain-backed settings with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`.
|
||||
- Registered the `recipe` URL scheme and wired SwiftUI `.onOpenURL` to forward only `recipe://callback` URLs to the active AppAuth external user-agent session.
|
||||
- Added `iosApp/Podfile` with `AppAuth` so the iOS shell has explicit pod integration alongside the existing KMP CocoaPods block.
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1: Implement iOS AppAuth OidcClient actual and CocoaPods bridge** - `ac9fc61` (feat)
|
||||
2. **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 the `AppAuth` pod.
|
||||
- `iosApp/iosApp/Info.plist` - `CFBundleURLTypes` registration for the `recipe` custom URL scheme.
|
||||
- `iosApp/iosApp/iOSApp.swift` - SwiftUI `.onOpenURL` forwarding for `recipe://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:compileKotlinIosSimulatorArm64` cannot pass after adding only `OidcClient.ios.kt` because the common `SecureAuthStateStore` expect 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 `OIDAuthState` as `NSSecureCoding`; it does not expose the Android-style `serialize()` / JSON-deserialize API assumed by the plan.
|
||||
- **Fix:** Persisted a JSON wrapper containing the full `NSKeyedArchiver` secure archive of `OIDAuthState`, preserving the common opaque `authStateJson` contract 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/Podfile` did not exist even though the plan listed it in `read_first`; it was created in Task 1.
|
||||
- A parallel `git add` attempt briefly hit Git's index lock. Staging was retried sequentially; no repository state was lost.
|
||||
- `./gradlew :composeApp:linkDebugFrameworkIosSimulatorArm64` passed as an extra confidence check and confirmed `IosAppAuthBridge.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: `ac9fc61` and `88dc8d7` were found in git history.
|
||||
- Acceptance criteria: all Task 1 and Task 2 grep checks passed.
|
||||
- Plan-level verification: `./gradlew :composeApp:compileKotlinIosSimulatorArm64` passed.
|
||||
|
||||
---
|
||||
*Phase: 02-authentication-foundation*
|
||||
*Completed: 2026-04-28*
|
||||
Reference in New Issue
Block a user