critical priority high complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

The screen renders the BankID logo and brand-compliant visual identity as defined in BankID UX guidelines
A 'Sign in with BankID' CTA button is displayed and enabled on screen load
Tapping the CTA button immediately disables the button, transitions to a loading state, and launches the BankID flow within 500 ms
Platform detection determines whether to open BankID in an in-app WebView (if platform supports) or hand off to the system browser via url_launcher
During the BankID handshake, status text updates through defined phases: 'Launching BankID...', 'Waiting for BankID app...', 'Verifying your identity...'
When the app is backgrounded during authentication (user switches to BankID app), the screen remains in its current loading state and does not navigate away or reset
When the app is foregrounded after the user completes the BankID flow, the callback handling resumes automatically without user re-interaction
The Riverpod BankIDAuthState notifier is the single source of truth for screen state transitions
A configurable timeout (default 120 s) transitions the screen to an error state if no callback is received
The screen passes widget tests for the idle → loading → awaitingCallback state transition sequence

Technical Requirements

frameworks
Flutter
Riverpod
webview_flutter (for in-app WebView path)
url_launcher (for system browser path)
apis
BankID OIDC / REST authentication API
AppLifecycleState listener for background/foreground detection
data models
BankIDAuthState (idle | launching | waitingForApp | verifying | success | error | timedOut)
BankIDLaunchMode (webView | systemBrowser)
BankIDSessionParams (sessionId, redirectUri, nonce, state)
performance requirements
CTA tap to first loading frame must be < 16 ms (one frame)
BankID launch URL must be opened within 500 ms of tap
App lifecycle resume event must trigger callback resumption within 200 ms
security requirements
BankID session parameters (nonce, state) must be cryptographically random (dart:math SecureRandom or crypto package)
WebView must disable JavaScript file access and content access to prevent data leakage
WebView must enforce HTTPS-only navigation with certificate pinning for BankID domains
Session parameters must not be stored in insecure SharedPreferences — use flutter_secure_storage
BankID session must be invalidated server-side if the timeout fires before callback arrival
ui components
BankIDLogoWidget (SVG or PNG brand asset)
AppButton (primary) wired to BankIDAuthService.initiateLogin()
PhaseStatusText widget that animates between status phase strings
CircularProgressIndicator or branded loading animation
WebView widget (webview_flutter) for in-app flow path
AppLifecycleStateObserver mixin or WidgetsBindingObserver for background detection

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use a WidgetsBindingObserver mixin on the screen's State (or a dedicated Riverpod lifecycle provider) to listen to AppLifecycleState changes. When the app transitions to paused, store a boolean flag in the BankIDAuthStateNotifier (isBackgrounded = true). When resumed, check if a pending session is active and re-subscribe to the deep-link stream. Do not cancel the timeout timer on backgrounding — the 120 s window should continue counting regardless of whether the app is in foreground.

The status text phase progression can be driven by a timer within the notifier: launch → 2 s → waitingForApp → once BankID response detected → verifying. Use a Stream from BankIDAuthService that the notifier listens to for status updates. For the WebView path, use webview_flutter's NavigationDelegate to intercept the BankID redirect URI and complete the flow without opening the external browser. For the system browser path, use url_launcher with launchMode: LaunchMode.externalApplication.

Platform selection logic should be encapsulated in BankIDAuthService.determineLaunchMode() — do not put platform detection logic in the widget layer. This task is high complexity primarily due to the backgrounding lifecycle edge case — invest time in thorough manual lifecycle testing.

Testing Requirements

Widget tests: (1) tap CTA → assert button disabled and loading indicator visible; (2) state transitions idle → launching → waitingForApp each produce correct status text; (3) simulate AppLifecycleState.paused → AppLifecycleState.resumed and assert state is preserved (no reset). Unit tests for BankIDAuthService: (1) initiateLogin() returns a valid launch URL containing nonce and state; (2) nonce and state are unique across 100 invocations. Platform selection tests: mock the platform to iOS/Android and assert the correct launch mode (WebView vs url_launcher) is chosen. Security tests: assert nonce/state values are not present in any debug log output.

Manual device testing: run on physical iOS device and complete a full BankID flow including the app-switch to the BankID app, backgrounding the test app, and returning — verify loading state is preserved.

Component
BankID Authentication Screen
ui high
Epic Risks (3)
high impact medium prob technical

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.

medium impact medium prob technical

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.

low impact high prob technical

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.