From e83d247cf9909c865c09c876883818332b9485da Mon Sep 17 00:00:00 2001 From: "simon.franken" Date: Wed, 25 Mar 2026 10:50:09 +0100 Subject: [PATCH] documents ios-authentication --- docs/ios-authentication.md | 128 +++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 docs/ios-authentication.md diff --git a/docs/ios-authentication.md b/docs/ios-authentication.md new file mode 100644 index 0000000..a3b8983 --- /dev/null +++ b/docs/ios-authentication.md @@ -0,0 +1,128 @@ +# iOS Authentication & Backend Communication Architecture + +## Overview +This document outlines the authentication mechanism and backend communication protocol used by the iOS application. The architecture relies on an API-driven approach where the backend acts as an intermediary (BFF - Backend for Frontend) for an OIDC identity provider. Notably, the backend manages the PKCE (Proof Key for Code Exchange) generation and verification internally, simplifying the mobile client's responsibilities. + +## 1. Authentication Flow + +The authentication process utilizes `ASWebAuthenticationSession` for a secure, out-of-app browser experience. + +### Step 1: Login Initiation +The application initiates the login sequence by launching an ephemeral web session targeting the backend's login endpoint. + +* **URL:** `[API_BASE_URL]/auth/login` +* **Query Parameters:** + * `redirect_uri`: `timetracker://oauth/callback` +* **Behavior:** The backend generates PKCE parameters, stores them in an in-memory session tied to a `state` parameter, and redirects the user to the actual Identity Provider (IdP). + +### Step 2: User Authentication +The user completes the authentication flow (e.g., entering credentials, 2FA) within the secure web view. + +### Step 3: Callback +Upon successful authentication, the backend redirects the browser back to the application using a custom URL scheme. + +* **Callback URL:** `timetracker://oauth/callback?code=[auth_code]&state=[state]` +* **Action:** The application intercepts this URL, extracts the `code` and `state` parameters. + +### Step 4: Token Exchange +The application immediately exchanges the received code and state for a JWT access token via a backend API call. + +* **Endpoint:** `POST /auth/token` +* **Headers:** `Content-Type: application/json` +* **Body (JSON):** + ```json + { + "code": "", + "state": "", + "redirect_uri": "timetracker://oauth/callback" + } + ``` +* **Note:** The `code_verifier` is **not** sent by the client. The backend retrieves the verifier using the `state` parameter from its internal session cache. +* **Success Response:** Returns a `TokenResponse` containing the `access_token` and the `User` object. + +## 2. API Communication + +All subsequent communication with the backend requires the obtained JWT access token. + +### Base Configuration +* **Base URL:** Determined via the `API_BASE_URL` key in `Info.plist`. Defaults to `http://localhost:3001` if missing. +* **Path Resolution:** API paths are appended to the Base URL (e.g., `/clients`, `/time-entries`). + +### Standard Request Headers +For authenticated endpoints, the following headers **must** be included: + +```http +Authorization: Bearer +Content-Type: application/json +Accept: application/json +``` + +### Response & Error Handling +* **Success (200-299):** Parses the JSON response into the expected model. Empty responses (`Data.isEmpty`) should be handled gracefully (e.g., mapping to a dummy `{}` object or allowing `Void` returns). +* **Unauthorized (401):** + * The access token has expired or is invalid. + * **Action:** The application must immediately clear the local session (keychain, user state) and present the login screen. +* **Standard Errors (400, 500, etc.):** + * The backend typically returns a JSON payload containing an `error` string. + * **Format:** `{"error": "Message detailing the failure"}` + +## 3. Storage & Session Management + +Security and seamless user experience dictate how the session is managed across app launches. + +### Token Storage +* The `access_token` must be stored securely using the **iOS Keychain**. +* **Service Name:** `com.timetracker.app` +* **Key:** `accessToken` +* **Accessibility:** `.whenUnlockedThisDeviceOnly` (prevents extraction when the device is locked and disables iCloud Keychain syncing). +* **In-Memory Cache:** The token is also cached in-memory during the app's lifecycle to minimize Keychain read operations. + +### Session Restoration (App Launch) +When the application starts: +1. Read the `accessToken` from the Keychain. +2. If present, make a verification request to `GET /auth/me`. +3. **If `GET /auth/me` succeeds:** Update the local `User` state and transition to the authenticated interface. +4. **If `GET /auth/me` fails (especially 401):** Clear the Keychain and transition to the unauthenticated login interface. + +### Logout +When the user explicitly logs out: +1. Send a best-effort `POST /auth/logout` request to the backend. +2. Immediately delete the `accessToken` from the Keychain. +3. Clear the in-memory `User` and token state. +4. Return to the login screen. + +## 4. Core Data Models + +### Token Response +```json +{ + "access_token": "eyJ...", + "token_type": "Bearer", + "expires_in": 3600, + "user": { ... } +} +``` + +### User Model +```json +{ + "id": "uuid-string", + "username": "johndoe", + "fullName": "John Doe", // Optional/Nullable + "email": "john@example.com" +} +``` + +## 5. Reimplementation Requirements List + +When rebuilding the iOS application, the following requirements **must** be met to ensure compatibility with the existing backend: + +- [ ] **R1. Custom Scheme Configuration:** Register `timetracker://` as a custom URL scheme in the project settings (Info.plist) to handle OAuth redirects. +- [ ] **R2. Ephemeral Web Sessions:** Use `ASWebAuthenticationSession` with `prefersEphemeralWebBrowserSession = true` to prevent Safari from sharing cookies with the app's login flow. +- [ ] **R3. Login Initiation:** The login URL must strictly be `[API_BASE_URL]/auth/login?redirect_uri=timetracker://oauth/callback`. +- [ ] **R4. Token Exchange Parameters:** The callback payload to `POST /auth/token` must include `code`, `state`, and `redirect_uri`. Do not attempt to implement local PKCE generation. +- [ ] **R5. Secure Storage:** The JWT access token must be stored exclusively in the Keychain using the `.whenUnlockedThisDeviceOnly` accessibility level. +- [ ] **R6. Authorization Header:** Every authenticated API request must include the `Authorization: Bearer ` header. +- [ ] **R7. Global 401 Interceptor:** Implement a global network interceptor or centralized error handler that catches `401 Unauthorized` responses, clears local storage, and forces a logout. +- [ ] **R8. Session Verification:** On application launch, if a token exists in the Keychain, the app must validate it by calling `GET /auth/me` before allowing access to secure screens. +- [ ] **R9. Error Parsing:** API error responses must be parsed to extract the `{"error": "..."}` message for user-facing alerts. \ No newline at end of file