critical priority low complexity infrastructure pending backend specialist Tier 1

Acceptance Criteria

Concrete class FlutterSecureStorageAdapter implements SecureStorageAdapter in lib/core/storage/flutter_secure_storage_adapter.dart
IOSOptions is configured with accessibility: KeychainAccessibility.first_unlock_this_device and synchronizable: false
AndroidOptions is configured with encryptedSharedPreferences: true
read() passes the derived string key and platform options to FlutterSecureStorage.read() and returns the nullable result
write() passes the derived string key, value, and platform options to FlutterSecureStorage.write()
delete() passes the derived string key and platform options to FlutterSecureStorage.delete()
clear() calls FlutterSecureStorage.deleteAll() with platform options
Platform exceptions from flutter_secure_storage are caught and re-thrown as typed StorageException with a descriptive message
Unit tests cover: write then read returns the written value; write then delete then read returns null; clear wipes all keys; a simulated platform exception is re-thrown as StorageException
All unit tests pass with flutter test and zero failures
dart analyze reports zero issues

Technical Requirements

frameworks
Flutter
flutter_secure_storage
flutter_test
Riverpod
apis
FlutterSecureStorage.read
FlutterSecureStorage.write
FlutterSecureStorage.delete
FlutterSecureStorage.deleteAll
data models
SecureStorageKey
SecureStorageAdapter
StorageException
performance requirements
No synchronous blocking calls — all operations must be awaited
Platform options objects should be created once (final fields) not on every call to avoid repeated object allocation
security requirements
encryptedSharedPreferences: true on Android ensures AES-256 encryption backed by Android Keystore
first_unlock_this_device on iOS prevents token access before first unlock after reboot — appropriate for auth tokens
synchronizable: false on iOS prevents tokens from being backed up to iCloud or migrated to a new device
PlatformException messages must not be forwarded verbatim to UI layers — wrap them in StorageException with sanitized messages

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Inject FlutterSecureStorage as a constructor parameter (not created internally) so it can be mocked in tests: FlutterSecureStorageAdapter({FlutterSecureStorage? storage}) with storage ??= const FlutterSecureStorage(). Create the _iosOptions and _androidOptions as final private fields in the constructor body. Use a try/catch around every flutter_secure_storage call — PlatformExceptions can occur due to Keystore corruption, device policy restrictions, or the user uninstalling and reinstalling the app (Android backup issues).

Log errors at the error level via the app logger before rethrowing as StorageException. The Riverpod provider registration belongs in task-001's providers file — this task only implements the class.

Testing Requirements

Unit tests using flutter_test and Mockito or mocktail to mock FlutterSecureStorage. Test file at test/core/storage/flutter_secure_storage_adapter_test.dart. Required test cases: (1) write + read round-trip returns correct value; (2) write + delete + read returns null; (3) clear + read on any key returns null; (4) PlatformException during read is caught and StorageException is thrown; (5) PlatformException during write is caught and StorageException is thrown. Do not test IOSOptions/AndroidOptions values directly — they are private implementation details.

Aim for 100% line coverage of FlutterSecureStorageAdapter.

Component
Secure Storage Adapter
infrastructure low
Epic Risks (3)
high impact medium prob technical

Flutter Secure Storage behavior differs between iOS Keychain and Android Keystore — key accessibility attributes (kSecAttrAccessibleWhenUnlocked vs. WhenUnlockedThisDeviceOnly) may cause tokens to become inaccessible after device restart or OS upgrade, breaking session restoration for returning users.

Mitigation & Contingency

Mitigation: Define explicit Keychain accessibility attributes during implementation and write integration tests on both platforms. Follow flutter_secure_storage documentation for cross-platform accessibility configuration.

Contingency: Implement a recovery flow that detects secure storage read failures and falls back to full re-authentication rather than crashing. Add a migration utility to re-write tokens with corrected attributes if a misconfiguration is discovered post-release.

high impact medium prob security

Personnummer is a legally sensitive national identifier under Norwegian GDPR implementation. If encryption-at-rest or data minimization requirements are not met before launch, the feature could be blocked by legal/compliance review from any of the four partner organizations.

Mitigation & Contingency

Mitigation: Ensure personnummer is only persisted after explicit user consent via the personnummer confirmation widget. Use Supabase column-level encryption for the personnummer field. Document the data processing basis and retention policy before the first TestFlight release.

Contingency: If legal review blocks the personnummer write-back, implement the feature as opt-in only with a deferred sync model, allowing BankID/Vipps login to proceed without storing the personnummer until compliance is confirmed.

medium impact high prob dependency

If the VippsOrgCostConfig data is not populated in Supabase for all four partner organizations before the feature ships, users from unconfigured organizations will see no Vipps login option and may report it as broken, creating confusion and support load.

Mitigation & Contingency

Mitigation: Create a seed migration script for Vipps org configuration and include it in the deployment checklist. Implement a clear admin UI warning when an organization is missing Vipps configuration.

Contingency: Add a feature flag in VippsOrgCostConfig so individual organizations can be enabled/disabled without a code deploy, allowing rapid remediation.