From 48cd82ab4fe2ac9330bdcdc6720b04e98925554b Mon Sep 17 00:00:00 2001 From: Simon Franken Date: Thu, 19 Feb 2026 19:00:16 +0100 Subject: [PATCH] Fix token loss: cache JWT in-memory, log keychain errors Keychain writes silently failed (missing keychain-access-groups entitlement on simulator), causing the token to disappear between handleTokenResponse and the first API call. The in-memory cache ensures the token is always available within the session; the keychain still persists it across launches when entitlements allow. --- .../TimeTracker/Core/Auth/AuthManager.swift | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/ios/TimeTracker/TimeTracker/Core/Auth/AuthManager.swift b/ios/TimeTracker/TimeTracker/Core/Auth/AuthManager.swift index 83985f1..124f459 100644 --- a/ios/TimeTracker/TimeTracker/Core/Auth/AuthManager.swift +++ b/ios/TimeTracker/TimeTracker/Core/Auth/AuthManager.swift @@ -14,14 +14,34 @@ final class AuthManager: ObservableObject { private let keychain: Keychain private let apiClient = APIClient() + /// In-memory cache so the token is always available within the current session, + /// even if the keychain write fails (e.g. missing entitlement on simulator). + private var _accessToken: String? + /// The backend-issued JWT. Sent as `Authorization: Bearer ` on every API call. var accessToken: String? { - get { try? keychain.get(AppConstants.KeychainKeys.accessToken) } + get { + // Return the in-memory value first; fall back to keychain for persistence + // across app launches. + if let cached = _accessToken { return cached } + let stored = try? keychain.get(AppConstants.KeychainKeys.accessToken) + _accessToken = stored + return stored + } set { + _accessToken = newValue if let value = newValue { - try? keychain.set(value, key: AppConstants.KeychainKeys.accessToken) + do { + try keychain.set(value, key: AppConstants.KeychainKeys.accessToken) + } catch { + logger.warning("Keychain write failed (token still available in-memory): \(error)") + } } else { - try? keychain.remove(AppConstants.KeychainKeys.accessToken) + do { + try keychain.remove(AppConstants.KeychainKeys.accessToken) + } catch { + logger.warning("Keychain remove failed: \(error)") + } } } } @@ -75,6 +95,7 @@ final class AuthManager: ObservableObject { func clearAuth() { logger.info("clearAuth — wiping token and user") + _accessToken = nil accessToken = nil currentUser = nil isAuthenticated = false