Create the shared contract and dependency foundation for Authentication Foundation.
Purpose: every downstream plan needs the same DTOs, dependency aliases, and Authentik provider contract before implementation starts.
Output: shared DTO/config files, build dependency wiring, and docs/authentik-setup.md.
@.planning/PROJECT.md
@.planning/ROADMAP.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
@AGENTS.md
Task 1: Add shared DTO/config contract and serialization test
- shared/build.gradle.kts
- shared/src/commonMain/kotlin/dev/ulfrx/recipe/Constants.kt
- shared/src/commonTest/kotlin/dev/ulfrx/recipe/SharedCommonTest.kt
- .planning/phases/02-authentication-foundation/02-CONTEXT.md (D-11, D-27, D-28)
shared/build.gradle.kts, shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/Constants.kt, shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/User.kt, shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/MeResponse.kt, shared/src/commonTest/kotlin/dev/ulfrx/recipe/shared/dto/MeResponseSerializationTest.kt
- `Constants.OIDC_REDIRECT_URI` equals exactly `recipe://callback` per D-09.
- `Constants.OIDC_ISSUER` ends with `/`; use placeholder `https://auth.example.invalid/application/o/recipe/` until real homelab value is substituted.
- `Constants.OIDC_CLIENT_ID` equals `recipe-app`.
- `MeResponse` serializes fields `id`, `sub`, `email`, `displayName`, and maps to `User`.
- `shared/commonMain` imports only allowed dependencies: Kotlin stdlib and kotlinx.serialization.
Apply `alias(libs.plugins.kotlinSerialization)` to `shared/build.gradle.kts`; add `api(libs.kotlinx.serializationJson)` in `commonMain.dependencies`.
Add `dev.ulfrx.recipe.shared.Constants` as a public object with `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_REDIRECT_URI`, and `API_BASE_URL`. Add `dev.ulfrx.recipe.shared.dto.User` and `MeResponse` as public `@Serializable` data classes using `String` for the server UUID, with `MeResponse.toUser()`.
Create `MeResponseSerializationTest` covering round trip, `displayName` wire name, and `ignoreUnknownKeys` compatibility with future `householdId`.
./gradlew :shared:jvmTest :shared:compileCommonMainKotlinMetadata
- `grep -q 'OIDC_REDIRECT_URI: String = "recipe://callback"' shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/Constants.kt`
- `grep -q '@Serializable' shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/MeResponse.kt`
- `grep -q 'public fun toUser()' shared/src/commonMain/kotlin/dev/ulfrx/recipe/shared/dto/MeResponse.kt`
- `./tools/verify-shared-pure.sh` exits 0
- `./gradlew :shared:jvmTest :shared:compileCommonMainKotlinMetadata` exits 0
Shared config and DTO contract exists and is tested without violating shared module purity.
Task 2: Add Phase 2 dependency aliases without Ktor patch bump
- gradle/libs.versions.toml
- composeApp/build.gradle.kts
- server/build.gradle.kts
- .planning/phases/02-authentication-foundation/02-RESEARCH.md (Standard Stack, Open Questions)
gradle/libs.versions.toml, composeApp/build.gradle.kts, server/build.gradle.kts
In `gradle/libs.versions.toml`, keep existing `ktor = "3.4.1"` unchanged. Add version keys and aliases for:
`appauth = "0.11.1"`, `androidx-security-crypto = "1.1.0"`, `multiplatformSettings = "1.3.0"`, `exposed = "0.55.0"`, `hikari = "6.2.1"`, plus `kotlinCocoapods` plugin.
Add libraries: `appauth`, `androidx-security-crypto`, `multiplatform-settings`, `multiplatform-settings-coroutines`, Ktor client core/auth/content-negotiation/logging/okhttp/darwin/cio, `ktor-serializationKotlinxJsonMpp` using `io.ktor:ktor-serialization-kotlinx-json`, Ktor server auth/auth-jwt/call-logging/status-pages, Exposed core/jdbc/java-time, Hikari, `kotlinx-serializationJson`.
In `composeApp/build.gradle.kts`, apply `alias(libs.plugins.kotlinSerialization)` and `alias(libs.plugins.kotlinCocoapods)`. Add common deps for settings, Ktor client, serialization, and platform deps: Android AppAuth + AndroidX Security Crypto + OkHttp, iOS Darwin, JVM CIO. Configure CocoaPods with `podfile = project.file("../iosApp/Podfile")`, framework `baseName = "ComposeApp"`, `isStatic = true`, and `pod("AppAuth") { version = "2.0.0" }`.
In `server/build.gradle.kts`, add server auth/JWT/call logging/status pages, Exposed, Hikari, and serialization deps from catalog. Do not add inline versions in build files.
Provider section must specify: OAuth2/OIDC public client, authorization code with PKCE S256, no client secret in the app, redirect URI `recipe://callback`, RS256 signing, single-string `aud` equal to `recipe-app`, JWKS URI from the provider's OpenID configuration, and end-session endpoint.
Scopes section must state the app requests exactly `openid profile email offline_access` and that Authentik must map/allow `offline_access` for refresh tokens. Manual UAT must cover fresh iOS login, reopen/refresh after access-token expiry, logout returning to login, and curl/HTTP verification of `/api/v1/me` returning 200 with valid token and 401 without/wrong-audience token.
Source Audit must mark all Phase 2 sources covered: GOAL Phase 2, REQ AUTH-01..AUTH-06, RESEARCH constraints, CONTEXT D-01..D-34, UI-SPEC auth screens, VALIDATION Wave 0 tests, PATTERNS file map. Deferred ideas must be listed as excluded: Universal Links/App Links, real Desktop OIDC, Wasm OIDC, Apple Sign-in, Authentik automation.
Mobile app launches system browser and receives authorization callback through custom URL scheme
app -> OS secure storage
Refresh tokens cross from process memory to persistent device storage
client -> server
Bearer access tokens cross HTTP boundary
STRIDE Threat Register
Threat ID
Category
Component
Disposition
Mitigation Plan
T-02-01-01
Spoofing/Elevation
OIDC provider setup
mitigate
Document public client + PKCE S256 + AppAuth state handling + exact recipe://callback registration
T-02-01-02
Information Disclosure
token storage dependencies
mitigate
Explicit AndroidX Security Crypto and iOS Keychain store plan; forbid no-arg Settings() for auth tokens
T-02-01-03
Elevation
JWT audience config
mitigate
Document single-string aud equal to recipe-app; server tests in Plan 02 enforce wrong audience 401
T-02-01-04
Information Disclosure
logs/docs
mitigate
Docs state never log Authorization or token bodies; server/client implementation plans include redaction
</threat_model>
Run `./gradlew :shared:jvmTest :shared:compileCommonMainKotlinMetadata`, `./tools/verify-shared-pure.sh`, and `./tools/verify-no-version-literals.sh`.
<success_criteria>
Downstream server, client, and UI plans have stable imports/config, Authentik setup is documented, Ktor remains at 3.4.1, and Android token security is explicit.
</success_criteria>
After completion, create `.planning/phases/02-authentication-foundation/02-01-SUMMARY.md`.