critical priority high complexity integration pending integration specialist Tier 3

Acceptance Criteria

BankID session initiation request is made server-side via Supabase Edge Function — mobile client never calls BankID API directly with credentials
Edge Function returns a sessionId/orderRef and a BankID autoStartToken or QR code URI to the mobile client
Mobile client stores the sessionId in flutter_secure_storage under a namespaced key (bankid_session_ref_v1)
BankID app deep link (bankid:///?autostarttoken=...&redirect=...) is launched via url_launcher
If BankID app is not installed, a QR code is displayed using qr_flutter as fallback for desktop BankID or BankID on another device
AuthState transitions to loading immediately upon session initiation
A 3-minute session timeout timer is started; if no assertion received within 3 minutes, AuthState.error is emitted and sessionId is cleared
User can cancel the flow at any time, triggering an explicit cancellation request to the Edge Function and transitioning to AuthState.unauthenticated
Redirect URI registered in BankID portal matches the app's custom scheme and is validated before launch
Unit tests verify sessionId storage, deep link construction, and timeout behavior

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
flutter_secure_storage
url_launcher
qr_flutter
apis
BankID OIDC / REST API (via Supabase Edge Function)
Supabase Edge Functions (Deno)
performance requirements
Edge Function call to receive sessionId under 2 seconds
Deep link launch within 300ms of receiving sessionId
QR code rendered within 100ms of receiving QR URI
security requirements
BankID client credentials stored exclusively in Edge Function environment variables — never in Flutter
SessionId treated as sensitive — stored in flutter_secure_storage, never logged
PKCE required: authorization code interception on mobile deep links must be mitigated
BankID session tokens not persisted beyond assertion polling — only the sessionId reference is kept
Deep link redirect URI must use the app's custom scheme registered in AndroidManifest.xml and Info.plist, not a plain http:// URI
GDPR: personnummer processing via BankID requires explicit DPA and lawful basis documented before production
ui components
LoadingOverlay (initiating session)
QRCodeWidget (BankID on another device fallback)
CancelButton (during BankID flow)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

BankID in Norway uses a REST-based OIDC flow. The initiation creates an 'order' server-side; the mobile client receives an autoStartToken used to launch the BankID app. Implement the Edge Function as POST /functions/v1/bankid-initiate, which calls the BankID API and returns {sessionId, autoStartToken, qrCodeUri} to the mobile client. On Flutter side: store sessionId → launch deep link → start 3-minute Dart Timer → navigate to a 'Waiting for BankID' screen.

The waiting screen should show an animated spinner and a cancel button. The QR code fallback is important for users who authenticate with BankID on a desktop browser — display the qrCodeUri via qr_flutter. Ensure the BankID autoStartToken deep link format matches the Norwegian BankID specification: bankid:///?autostarttoken={token}&redirect={encoded_redirect_uri}. The redirect URI after BankID completion should route back to the app and trigger task-008.

Testing Requirements

Unit tests (flutter_test): mock Edge Function response with a valid sessionId and autoStartToken, verify flutter_secure_storage.write() is called and url_launcher receives the correct BankID URI. Test QR code fallback: when url_launcher returns false (app not installed), verify QRCodeWidget receives the QR URI. Test timeout: advance timer mock past 3 minutes, verify AuthState.error and storage cleared. Test cancellation: tap cancel, verify Edge Function cancellation is called and storage cleared.

Integration tests: call the BankID Edge Function in sandbox environment, verify sessionId returned. Security tests: verify sessionId is not present in any debug logs or analytics events. Target 90%+ branch coverage.

Component
BankID Authentication Service
service high
Epic Risks (4)
high impact medium prob technical

The PKCE OAuth flow requires the code verifier to survive an app backgrounding during the Vipps redirect, which can trigger OS memory pressure and clear in-memory state. If the verifier is lost between authorization request and callback, the token exchange fails and the user is stranded with a confusing error.

Mitigation & Contingency

Mitigation: Store the PKCE code verifier in AuthTokenStore (Flutter Secure Storage) immediately after generation, before launching the Vipps redirect. Clear it only after a successful or explicitly failed token exchange.

Contingency: If state loss occurs in production, implement a retry flow that generates a new PKCE pair and restarts the authorization URL request, with a user-visible 'Try again' prompt rather than a generic error.

medium impact medium prob technical

Resuming a Supabase session after biometric verification requires the session token to still be valid. If the session has expired in the background (e.g., after a long device offline period), biometric success will not produce a valid session, and the user will see a confusing 'Face ID worked but still logged out' experience.

Mitigation & Contingency

Mitigation: Before presenting the biometric prompt, check session token expiry. If expired, skip biometrics and route directly to full BankID/Vipps re-authentication. Only offer biometric re-auth if the stored refresh token is still within its validity window.

Contingency: If session expiry during biometric flow occurs in production, implement a graceful transition message ('Your session has expired — please log in again') that preserves the user's last-used authentication method preference.

high impact medium prob integration

BankID and Vipps may return different user identifiers (personnummer, phone number, sub claim) that must be correctly linked to an existing Supabase auth user. If the linking logic has edge cases (e.g., user previously registered via email/password), duplicate Supabase accounts may be created.

Mitigation & Contingency

Mitigation: Design the identity linking logic with explicit disambiguation: check for existing users by personnummer before creating a new Supabase identity. Implement the linking via Supabase Edge Function to keep the logic server-side and auditable.

Contingency: Implement an admin-facing account merge tool in the admin portal to resolve duplicate accounts if they occur. Add a Supabase unique constraint on the personnummer field to make duplicates fail loudly rather than silently.

medium impact high prob dependency

The Vipps nin (personnummer) scope requires explicit approval from Vipps as part of the merchant agreement. If this scope approval is not in place before the production release, the Vipps flow will succeed but return no personnummer, making the primary business value (membership data gap fill) non-functional without user-visible error.

Mitigation & Contingency

Mitigation: Apply for Vipps nin scope approval as part of the merchant onboarding process, well before Phase 2 launch. Implement the service to gracefully handle absent nin claims and show users a clear message if personnummer could not be retrieved.

Contingency: If nin scope is delayed, ship the Vipps login flow without personnummer write-back first (delivering login value immediately) and add personnummer sync as a post-approval update with no UI changes required.