Biometric Auth Screen — fallback routing and unsupported device handling
epic-bankid-vipps-login-ui-task-008 — Add fallback logic to the Biometric Authentication Screen for devices where biometrics are unavailable or not enrolled. When biometrics are unsupported, display a clear explanation banner and route the user to BankID or Vipps re-authentication. When biometrics are available but not enrolled, show a prompt encouraging setup with a deep-link to device settings. Handle biometric lockout after multiple failures gracefully.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Use the BLoC pattern with a dedicated BiometricAuthBloc. On screen init, dispatch a CheckBiometricCapability event; the bloc calls a BiometricService abstraction (wraps local_auth) and emits the appropriate state. Avoid putting platform logic directly in the widget tree. For the iOS deep-link use 'App-Prefs:root=TOUCHID_PASSCODE', for Android use AndroidIntent with Settings.ACTION_SECURITY_SETTINGS — wrap in a platform channel or use the app_settings package if already in dependencies.
The lockout counter (failureCount) lives in the BLoC state, not in shared preferences. When navigating back to the method selector after lockout, push-replace so the back button does not return to the locked screen. The not-enrolled enrollment prompt should be non-blocking — users can dismiss it and still proceed to BankID/Vipps without setting up biometrics.
Testing Requirements
Unit tests (flutter_test) for all BLoC state transitions: emit BiometricUnsupportedState when isDeviceSupported returns false; emit BiometricNotEnrolledState when supported but no biometrics enrolled; emit BiometricLockedOutState after 3 auth failures. Widget tests verify the correct widget subtree renders for each state and that the CTA button navigates to the correct route. Integration test (added in task-012) covers the full fallback path: biometric screen → locked out → method selector. Mock local_auth responses using a FakeBiometricService injected via constructor to avoid platform dependency during CI.
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.