128 lines
6.4 KiB
Markdown
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. |