critical priority low complexity database pending backend specialist Tier 1

Acceptance Criteria

AuthRepository.cacheSession(Session session) writes access_token and refresh_token to flutter_secure_storage under well-defined keys (e.g., 'auth.access_token', 'auth.refresh_token')
AuthRepository.loadCachedSession() returns a Session? — non-null if valid tokens exist, null if storage is empty or tokens are corrupted
AuthRepository.clearSession() deletes both keys and returns Future<void> — verified by subsequent loadCachedSession() returning null
Cached session survives a full app restart: write tokens, terminate the app, relaunch, assert loadCachedSession() returns non-null
If flutter_secure_storage throws a PlatformException (e.g., keychain unavailable), the error is caught, logged (debug only), and rethrown as a typed StorageException with a user-friendly message
clearSession() is called during explicit sign-out — integration test verifies session is absent after sign-out
No session data is stored in SharedPreferences, Hive, or any unencrypted store
On Android, storage uses EncryptedSharedPreferences (AES256); on iOS, Keychain — verified by inspecting flutter_secure_storage configuration

Technical Requirements

frameworks
Flutter
flutter_secure_storage
supabase_flutter
apis
Supabase GoTrue Auth API (Session object)
data models
Session (access_token: String, refresh_token: String, expires_at: int)
StorageException (message: String, cause: dynamic)
performance requirements
Read operation completes in under 100ms on mid-range devices
Write operation completes in under 200ms — acceptable for post-login storage
security requirements
Tokens stored exclusively in flutter_secure_storage — never in plain SharedPreferences or files
Android: set encryptedSharedPreferences: true in AndroidOptions
iOS: set accessibility: KeychainAccessibility.first_unlock_this_device in IOSOptions to ensure tokens are available after first unlock post-restart
Do not log token values, even the first/last 4 characters
Cleared tokens must be securely deleted — flutter_secure_storage delete() meets this requirement on both platforms

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

supabase_flutter 2.x already integrates with flutter_secure_storage for session persistence via the authFlowType and localStorage parameters in Supabase.initialize(). Before implementing a custom cache, verify whether Supabase.initialize(localStorage: SecureStorageLocalStorage()) already covers the persistence requirement — if so, AuthRepository only needs to call signOut() to clear. If custom control is needed (e.g., multi-org session management required by NHF's up-to-5-locallag membership), wrap the Supabase session localStorage with a prefixed key scheme per org. Key naming convention: 'auth.{orgId}.access_token'.

For the MVP (single org context), plain keys suffice. The iOS KeychainAccessibility setting is critical: first_unlock_this_device allows the app to read tokens after a restart without requiring the user to unlock the phone first — important for background refresh.

Testing Requirements

Unit tests with flutter_test using a MockFlutterSecureStorage (implement the FlutterSecureStorage interface): (1) cacheSession() calls write() for both token keys; (2) loadCachedSession() returns null when storage is empty; (3) loadCachedSession() returns reconstructed Session when both keys present; (4) clearSession() calls delete() for both keys; (5) PlatformException from write() is caught and rethrown as StorageException. Integration test on a real device (iOS via TestFlight, Android via direct install): write session, kill app, relaunch, assert tokens readable. Do not use emulators for keychain/keystore tests — they behave differently from physical devices.

Component
Authentication Repository
data low
Epic Risks (2)
high impact low prob integration

Supabase client initialization may fail silently in certain Flutter environments if environment variables are missing or the anon key is rotated, leading to runtime null-pointer errors throughout the auth layer.

Mitigation & Contingency

Mitigation: Add explicit assertion checks during app startup that verify the Supabase client is initialized before the router resolves. Document required --dart-define keys in the project README and add a CI step that validates their presence.

Contingency: Implement a fallback initialization error screen with a clear message and a retry button. Log initialization failures to crash reporting immediately.

medium impact medium prob technical

The flutter_secure_storage package behaviour differs between iOS Keychain and Android Keystore implementations. On Android, biometric-enrolled devices may require additional authentication to read stored tokens, causing unexpected session read failures.

Mitigation & Contingency

Mitigation: Test the repository on Android devices with and without biometric enrollment early in development. Use accessibility options in flutter_secure_storage to configure whether biometric authentication is required for storage access.

Contingency: If biometric-gated storage causes regressions, fall back to a non-biometric storage option for session tokens (reserving biometric-gated storage for higher-sensitivity credentials only).