Files
timetracker/docs/ios-authentication.md
2026-03-25 10:50:09 +01:00

128 lines
6.4 KiB
Markdown

# 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": "<auth_code>",
"state": "<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 <access_token>
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 <token>` 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.