Files
recipe/.planning/phases/02-authentication-foundation/02-05-PLAN.md
2026-04-29 21:07:49 +02:00

9.2 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
02-authentication-foundation 05 execute 3
02-01
02-03
composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt
composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.ios.kt
iosApp/iosApp/Info.plist
iosApp/iosApp/iOSApp.swift
iosApp/Podfile
true
AUTH-01
AUTH-02
AUTH-04
AUTH-05
truths artifacts key_links
iOS login uses AppAuth authorization-code flow with PKCE through system browser and recipe://callback
iOS requested scopes are exactly openid profile email offline_access
iOS persists full AppAuth AuthState JSON in Keychain with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
SwiftUI callback wiring forwards recipe://callback to the current AppAuth flow
iOS logout uses AppAuth end-session when metadata exposes an endpoint
path provides contains
composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt iOS AppAuth actual per D-01, D-04, D-16, D-19, D-20 OIDAuthorizationService
path provides contains
composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.ios.kt iOS Keychain storage per D-14 kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
path provides contains
iosApp/iosApp/Info.plist recipe URL scheme registration CFBundleURLSchemes
path provides contains
iosApp/iosApp/iOSApp.swift SwiftUI openURL callback forwarding to AppAuth onOpenURL
path provides contains
iosApp/Podfile AppAuth CocoaPod integration if required by chosen KMP CocoaPods setup AppAuth
from to via pattern
iosApp/iosApp/iOSApp.swift composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt openURL forwards callback to current AppAuth external user-agent session onOpenURL|currentAuthorizationFlow
from to via pattern
iosApp/iosApp/Info.plist composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt registered URL scheme matches redirect URI consumed by AppAuth CFBundleURLSchemes|recipe
Implement the iOS OIDC and secure storage actuals.

Purpose: satisfy iOS-primary AUTH-01/AUTH-02/AUTH-04/AUTH-05 behind the common contracts from Plan 02-03 without mixing Android work into the same execution plan. Output: iOS AppAuth OidcClient actual, iOS Keychain AuthState store, URL scheme registration, Swift callback wiring, and Podfile integration.

<execution_context> @/Users/rwilk/.codex/get-shit-done/workflows/execute-plan.md @/Users/rwilk/.codex/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/REQUIREMENTS.md @.planning/phases/02-authentication-foundation/02-CONTEXT.md @.planning/phases/02-authentication-foundation/02-RESEARCH.md @.planning/phases/02-authentication-foundation/02-VALIDATION.md @.planning/phases/02-authentication-foundation/02-PATTERNS.md @.planning/phases/02-authentication-foundation/02-01-SUMMARY.md @.planning/phases/02-authentication-foundation/02-03-SUMMARY.md @AGENTS.md @iosApp/iosApp/Info.plist @iosApp/iosApp/iOSApp.swift Task 1: Implement iOS AppAuth OidcClient actual and CocoaPods bridge - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.kt - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/auth/OidcResult.kt - shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/Constants.kt - iosApp/Podfile - .planning/phases/02-authentication-foundation/02-CONTEXT.md (D-01, D-04, D-05, D-06, D-09, D-16, D-19, D-20) composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt, iosApp/Podfile Implement iOS `actual class OidcClient` via AppAuth-iOS interop using `OIDAuthorizationService`, `OIDAuthState`, `OIDAuthorizationRequest`, token refresh/fresh-token helpers, and `OIDEndSessionRequest`. Use `suspendCancellableCoroutine` so cancellation cancels the current AppAuth request.
Request scopes exactly `openid`, `profile`, `email`, and `offline_access`. Serialize and deserialize the full `OIDAuthState` JSON blob per D-13. Refresh must use AppAuth fresh-token behavior and return updated AuthState JSON for AuthSession persistence. Logout must attempt RP-initiated end-session with `id_token_hint` when available; if end-session is unavailable or fails, surface no local-token-clearing responsibility here because AuthSession clears local state after calling logout.

Ensure AppAuth CocoaPod integration is present through the existing Gradle CocoaPods setup from Plan 02-01 and/or `iosApp/Podfile` as required by the repo's KMP CocoaPods wiring. Do not introduce an additional OIDC library.
./gradlew :composeApp:compileKotlinIosSimulatorArm64 - `grep -q 'OIDAuthorizationService' composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt` - `grep -q 'offline_access' composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt` - `grep -q 'suspendCancellableCoroutine' composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt` - `grep -q 'OIDEndSessionRequest' composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/OidcClient.ios.kt` - `grep -q 'AppAuth' iosApp/Podfile composeApp/build.gradle.kts` - `./gradlew :composeApp:compileKotlinIosSimulatorArm64` exits 0 iOS AppAuth login, refresh, and logout compile behind the common OidcClient contract. Task 2: Implement iOS Keychain store and callback wiring - composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.kt - iosApp/iosApp/Info.plist - iosApp/iosApp/iOSApp.swift - .planning/phases/02-authentication-foundation/02-CONTEXT.md (D-09, D-13, D-14, D-15) composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.ios.kt, iosApp/iosApp/Info.plist, iosApp/iosApp/iOSApp.swift Implement iOS `actual class SecureAuthStateStore` with Keychain read/write/delete for one opaque AuthState JSON string per app install. Use `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` exactly per D-14; do not store AuthState in UserDefaults or plaintext files.
Add `CFBundleURLTypes` to `iosApp/iosApp/Info.plist` registering scheme `recipe`, matching redirect URI `recipe://callback`.

Add SwiftUI `.onOpenURL` or an app delegate bridge in `iOSApp.swift` that forwards incoming `recipe://callback` URLs to the current AppAuth external user-agent session held by the KMP iOS OidcClient bridge. Keep existing Koin initialization intact.
./gradlew :composeApp:compileKotlinIosSimulatorArm64 - `grep -q 'kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly' composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth/SecureAuthStateStore.ios.kt` - `! grep -R 'NSUserDefaults\\|UserDefaults' composeApp/src/iosMain/kotlin/dev/ulfrx/recipe/auth` - `grep -q 'CFBundleURLSchemes' iosApp/iosApp/Info.plist` - `grep -q 'recipe' iosApp/iosApp/Info.plist` - `grep -q 'onOpenURL\\|application(.*open' iosApp/iosApp/iOSApp.swift` - `./gradlew :composeApp:compileKotlinIosSimulatorArm64` exits 0 iOS token storage is explicit and the custom URL callback is wired back into AppAuth.

<threat_model>

Trust Boundaries

Boundary Description
system browser -> iOS app Authorization code returns through custom URL scheme
iOS app -> Keychain AuthState JSON containing refresh token is persisted
Swift shell -> KMP auth bridge openURL callback crosses from SwiftUI into KMP/AppAuth flow state

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-02-05-01 Spoofing/Elevation custom URL callback mitigate AppAuth handles state/nonce and PKCE S256; Info.plist byte-matches recipe://callback
T-02-05-02 Information Disclosure iOS token store mitigate Keychain item uses kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; grep forbids UserDefaults in auth
T-02-05-03 Information Disclosure AppAuth diagnostics mitigate iOS actual must not log AuthState JSON, access tokens, refresh tokens, ID tokens, or Authorization headers
T-02-05-04 Spoofing Swift callback bridge mitigate onOpenURL forwards only registered callback URLs to the active AppAuth session
</threat_model>
Run `./gradlew :composeApp:compileKotlinIosSimulatorArm64`.

<success_criteria> iOS AppAuth login/refresh/logout, iOS Keychain AuthState persistence, URL scheme registration, and callback forwarding compile independently below the file-count threshold. </success_criteria>

After completion, create `.planning/phases/02-authentication-foundation/02-05-SUMMARY.md`.