Use environment-based auth manager with Apollo interceptors
Context
The iOS app makes authenticated GraphQL requests via Apollo Client. Authentication needs to be transparent to views — they shouldn't manage tokens, handle 401s, or know about auth state transitions. SwiftUI's Environment pattern provides dependency injection, and Apollo's interceptor chain handles request-level concerns.
Decision
A single @MainActor @Observable AuthService injected at the app root via SwiftUI Environment. Apollo interceptors handle token injection and auth error recovery transparently.
AuthService responsibilities:
- Owns auth state (unknown → unauthenticated → registered)
- Persists tokens in secure storage (iOS Keychain)
- Manages device ID for token binding
- Provides login, logout, session checking
- Clears Apollo cache on auth failure or logout
Apollo interceptors:
- Auth interceptor adds
Authorization: Bearer <token>andX-Device-IDheaders to all requests - Error interceptor catches auth failures and triggers re-authentication
Views access AuthService via @Environment(AuthService.self) and make GraphQL calls normally. Auth happens behind the scenes.
Consequences
Clean view code — views focus on business logic, not token management. Auth state is a single source of truth. Interceptors handle the mechanical parts (headers, refresh, error recovery) without view involvement.
The trade-off is that auth logic is less visible, which can make debugging harder. The interceptor chain is also tightly coupled to Apollo's client architecture.