BankID Auth Screen — callback handling and error states
epic-bankid-vipps-login-ui-task-006 — Implement the BankID deep-link and WebView callback handling on the BankID Authentication Screen: detect successful authentication responses, pass tokens to BankIDAuthService, and trigger navigation to the Personnummer Confirmation Widget. Handle all documented BankID error codes with specific, actionable user-facing messages. Include a Cancel action that cleanly aborts the BankID session and returns to the method selector.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Map BankID error codes in a dedicated bankid_error_mapper.dart file that converts raw error strings to BankIDError enum values and then to user-facing message strings. This separation makes it easy to add new codes or update copy without touching the notifier or UI logic. The Cancel button must be wired to an async action in the notifier: (1) set state to cancelling (disables button to prevent double-tap), (2) await BankIDAuthService.cancelSession(), (3) emit idle, then (4) signal the router to pop. Use ref.listen in the screen widget to respond to BankIDAuthState.success by navigating — keep navigation out of the notifier.
For the WebView path, implement a NavigationDelegate.onNavigationRequest that detects the redirect URI prefix and hands the URL to BankIDAuthService for parsing rather than allowing the WebView to navigate to it. This prevents the BankID redirect from loading in the WebView and instead routes it through the same callback handler used by deep links. This task is high complexity because BankID has many edge-case error codes and the server-side session cancellation adds async complexity to what appears to be a simple Cancel button — plan thorough testing.
Testing Requirements
Unit tests for BankIDAuthService: (1) valid callback with correct state → token exchange called, success emitted with personnummer; (2) callback with wrong state → csrfMismatch error emitted, no token exchange; (3) each documented BankID error code maps to the correct BankIDError enum value; (4) cancelSession() calls the BankID API abort endpoint. Widget tests: (1) simulate success callback → assert navigation to PersonnummerConfirmation with correct args; (2) simulate each error type → assert correct message string is displayed; (3) tap Cancel during awaitingCallback → assert cancelSession called and route popped; (4) tap Retry after error → assert state resets to idle; (5) assert error container has Semantics liveRegion true. Security tests: assert personnummer is not present in any captured log output during a success callback simulation. E2E manual test on device: complete a full BankID flow and verify navigation to Personnummer Confirmation; deliberately cancel mid-flow and verify return to method selector.
BankID on mobile uses a WebView or external app redirect that has known compatibility issues with Flutter's WebView package on certain Android versions. BankID's JavaScript-heavy broker pages may also trigger CSP or mixed-content errors in a Flutter WebView, preventing the authentication flow from completing.
Mitigation & Contingency
Mitigation: Use the flutter_inappwebview package (more mature than webview_flutter for complex OAuth pages) and validate BankID WebView rendering on the broker's test environment before integrating with the service layer. Prefer external browser redirect where the broker supports it.
Contingency: If WebView approach fails for certain BankID brokers, implement the full external browser redirect + deep link callback pattern as the primary flow and treat WebView as a fallback only.
The OAuth redirect flows (both Vipps and BankID) temporarily move the user outside the Flutter app into an external browser or the Vipps/BankID app. Screen reader users may lose focus context during this transition and become disoriented when the app callback returns them to the loading state, failing the WCAG 2.2 AA mandate.
Mitigation & Contingency
Mitigation: Implement explicit accessibility announcements (live region announcements) at each transition point: when launching the external flow ('Opening Vipps'), during the loading wait state ('Waiting for Vipps confirmation'), and on return ('Login successful' or 'Login failed — please try again'). Test with VoiceOver on iOS and TalkBack on Android during development.
Contingency: If OAuth transition accessibility is unresolvable on a specific platform, add an explicit accessibility user guide in the onboarding flow explaining the external app redirect behavior to set user expectations.
Biometric UI varies significantly across devices — Face ID (iPhone), fingerprint sensor (most Android), front-facing camera biometrics (some Android), and devices with no biometrics at all. Flutter's local_auth handles the OS dialog but the surrounding UI must gracefully handle all these cases, and testing coverage for all permutations is difficult.
Mitigation & Contingency
Mitigation: Use local_auth's getAvailableBiometrics() to detect the exact biometric type and render appropriate iconography (Face ID icon vs. fingerprint icon). For devices with no biometrics, skip the biometric screen entirely and route directly to full re-authentication.
Contingency: If a specific device configuration produces unexpected local_auth behavior in production, implement a user-accessible toggle in Settings to disable biometric login entirely, routing those users to the standard BankID/Vipps flow without biometrics.