6.4 KiB
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
stateparameter, 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
codeandstateparameters.
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):
{ "code": "<auth_code>", "state": "<state>", "redirect_uri": "timetracker://oauth/callback" } - Note: The
code_verifieris not sent by the client. The backend retrieves the verifier using thestateparameter from its internal session cache. - Success Response: Returns a
TokenResponsecontaining theaccess_tokenand theUserobject.
2. API Communication
All subsequent communication with the backend requires the obtained JWT access token.
Base Configuration
- Base URL: Determined via the
API_BASE_URLkey inInfo.plist. Defaults tohttp://localhost:3001if 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:
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 allowingVoidreturns). - 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
errorstring. - Format:
{"error": "Message detailing the failure"}
- The backend typically returns a JSON payload containing an
3. Storage & Session Management
Security and seamless user experience dictate how the session is managed across app launches.
Token Storage
- The
access_tokenmust 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:
- Read the
accessTokenfrom the Keychain. - If present, make a verification request to
GET /auth/me. - If
GET /auth/mesucceeds: Update the localUserstate and transition to the authenticated interface. - If
GET /auth/mefails (especially 401): Clear the Keychain and transition to the unauthenticated login interface.
Logout
When the user explicitly logs out:
- Send a best-effort
POST /auth/logoutrequest to the backend. - Immediately delete the
accessTokenfrom the Keychain. - Clear the in-memory
Userand token state. - Return to the login screen.
4. Core Data Models
Token Response
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"user": { ... }
}
User Model
{
"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
ASWebAuthenticationSessionwithprefersEphemeralWebBrowserSession = trueto 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/tokenmust includecode,state, andredirect_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
.whenUnlockedThisDeviceOnlyaccessibility 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 Unauthorizedresponses, 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/mebefore allowing access to secure screens. - R9. Error Parsing: API error responses must be parsed to extract the
{"error": "..."}message for user-facing alerts.