critical priority medium complexity integration pending integration specialist Tier 4

Acceptance Criteria

After successful biometric verification, SupabaseSessionManager.refreshSession() is called before BiometricAuthSuccess is emitted
If token refresh succeeds, BiometricAuthSuccess is returned with the fresh Session object attached
If token refresh fails with a network error, BiometricAuthTokenRefreshFailed is returned — biometric success is not propagated
If the Supabase session is already expired beyond refresh window, BiometricAuthSessionExpired is returned and user is directed to full re-login
Token refresh is not retried automatically — caller (Bloc) decides retry policy
Refresh token rotation is respected: the new refresh token from Supabase response replaces the cached one in flutter_secure_storage
JWT stored in flutter_secure_storage (iOS Keychain / Android Keystore), never in plain SharedPreferences
Integration test confirms full flow: mock biometric success → real SupabaseClient.auth.refreshSession() → valid Session returned
Unit test covers the token refresh failure path with a mocked SupabaseClient that throws AuthException

Technical Requirements

frameworks
Flutter
BLoC
flutter_local_auth
Riverpod
apis
Supabase Auth SDK (supabase_flutter)
SupabaseClient.auth.refreshSession()
flutter_secure_storage
performance requirements
Token refresh round-trip should complete within 3 seconds on normal connectivity
No duplicate refresh calls — guard against concurrent refresh triggers with a mutex or in-flight flag
security requirements
JWTs stored in flutter_secure_storage (iOS Keychain / Android Keystore) — never plain SharedPreferences
Refresh tokens rotated on every use — ensure old token is replaced immediately after successful refresh
Session invalidated server-side if suspicious activity detected — handle AuthException.sessionNotFound gracefully
PKCE flow used for OAuth redirects — do not store authorization codes
Service role key never exposed to mobile client — all token operations use anon key + user JWT

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Inject SupabaseClient (or a SupabaseSessionManager abstraction) into BiometricAuthService via constructor for testability. Use a completer/mutex pattern to prevent concurrent refresh races — if a refresh is already in flight, await the same Future rather than issuing a second request. Catch GoTrueException specifically to differentiate session expiry from network failures. Store the refreshed access token and refresh token via flutter_secure_storage immediately after SupabaseClient updates its internal state — do not rely solely on the Supabase SDK's internal persistence since the app uses flutter_secure_storage as the authoritative store.

Log token refresh outcome at debug level only — never log token values.

Testing Requirements

Unit tests with mocked SupabaseClient covering: successful refresh returns new Session, AuthException on expired refresh token returns BiometricAuthSessionExpired, network timeout returns BiometricAuthTokenRefreshFailed. Integration test using a real Supabase test project verifying token rotation (new refresh token differs from old). Test that flutter_secure_storage is written with the new JWT after successful refresh. Test concurrent call guard: two simultaneous authenticate() calls should result in only one refreshSession() network request.

Component
Biometric Authentication Service
service medium
Epic Risks (3)
high impact medium prob technical

Multiple concurrent callers (e.g., SessionResumeManager and a background sync service) could simultaneously detect a near-expired token and each invoke SupabaseSessionManager.refreshSession(), causing duplicate refresh API calls and potentially a token invalidation race condition on the Supabase Auth server. This can result in one caller receiving a valid refreshed token while another receives a 401, causing intermittent authentication failures.

Mitigation & Contingency

Mitigation: Implement a single-flight pattern inside SupabaseSessionManager so that concurrent refresh calls coalesce into one in-flight request. Use a Dart Completer or AsyncMemoizer to ensure all waiters receive the same refreshed token. Write a concurrent integration test to validate the single-flight behaviour.

Contingency: If the single-flight pattern introduces deadlocks or timeout complexity, fall back to a mutex-based lock with a 10-second timeout, logging a warning if the lock is held longer than expected, and triggering a full re-login if the refresh ultimately fails.

high impact low prob security

Supabase Row-Level Security policies evaluate the JWT claims (user_id, role, org_id) on every query. If the refreshed token contains stale or changed claims — for example if a coordinator's role was updated server-side — RLS may silently block data access even though the session appears valid from the client's perspective, causing confusing empty screens rather than an authentication error.

Mitigation & Contingency

Mitigation: After every token refresh, decode the new JWT and compare key claims (role, org_id) with the cached values. If claims have changed, emit a session-claims-changed event that triggers a role re-resolution and navigation reset. Document this behaviour in the SupabaseSessionManager API contract.

Contingency: If claims drift is detected in production and causes data visibility issues, provide a force-refresh mechanism in the UI (pull-to-refresh on home screen) that clears cached role state and re-fetches from Supabase, accompanied by a user-visible toast indicating the session was refreshed.

medium impact medium prob security

Allowing session resumption from cached local token when offline introduces a window where a revoked or invalidated session can still grant app access. For example, if a coordinator deactivates a peer mentor's account while the mentor is offline, the mentor continues to have access until connectivity is restored and the token is validated server-side.

Mitigation & Contingency

Mitigation: Set a maximum offline grace period (e.g., 24 hours) stored alongside the token in SecureSessionStorage. If the grace period is exceeded, force a full credential re-login regardless of connectivity status. Scope offline access to read-only operations only, requiring connectivity for any write that reaches Supabase.

Contingency: If the offline grace period logic is found to be insufficient for compliance, implement remote session invalidation via a lightweight push notification that clears SecureSessionStorage even when the app is backgrounded, using FCM with a data-only message.