critical priority medium complexity infrastructure pending infrastructure specialist Tier 1

Acceptance Criteria

generateOAuthState() returns a cryptographically random 32-byte hex string stored in flutter_secure_storage with a TTL of 10 minutes
generatePkceChallenge() returns a { codeVerifier, codeChallenge } pair where codeVerifier is 43-128 cryptographically random URL-safe chars and codeChallenge is SHA-256(codeVerifier) base64url-encoded
handleIncomingLink(uri) validates that the state parameter in the redirect URI matches the stored state; mismatch returns StateMismatchFailure
handleIncomingLink correctly parses the authorization code from both Vipps redirect URIs (e.g., no.myapp://vipps/callback?code=...&state=...) and BankID redirect URIs (e.g., no.myapp://bankid/callback?code=...&state=...)
handleIncomingLink routes the parsed result to VippsAuthService or BankIdAuthService based on the URI path segment
OAuth error parameter in the redirect URI (e.g., ?error=access_denied) is parsed and surfaced as a typed OAuthUserDeniedFailure or OAuthServerError
Custom URI scheme (e.g., no.myapp) is registered in AndroidManifest.xml and Info.plist
The stored state is deleted from secure storage immediately after validation (whether match or mismatch) to prevent reuse
PKCE codeVerifier is stored in flutter_secure_storage during the auth flow and deleted after use
Handler exposes a Stream<OAuthCallbackResult> that the auth service layer subscribes to; it does not call services directly (separation of concerns)
App link/Universal Link configuration is documented with required server-side /.well-known/apple-app-site-association and assetlinks.json entries if Universal Links are used

Technical Requirements

frameworks
Flutter
Riverpod
Dart
flutter_secure_storage
app_links or uni_links (deep link package)
apis
Vipps Login API (redirect URI registration)
BankID OIDC (redirect URI registration)
performance requirements
State validation must be synchronous after the secure storage read (< 50ms)
URI parsing and routing must complete before the next UI frame to avoid visible delay on app resume
security requirements
PKCE required to prevent authorization code theft on mobile deep links
State parameter validated on every callback — no exceptions
Stored state and codeVerifier deleted immediately after use to prevent replay
Custom URI scheme chosen to be unpredictable and org-specific (reverse domain notation)
State stored in flutter_secure_storage (iOS Keychain / Android Keystore), never plain SharedPreferences
PKCE flow used for OAuth redirects
Refresh tokens rotated on every use
Session invalidated server-side on suspicious activity

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use the app_links package (preferred over uni_links for null safety and maintenance) to subscribe to incoming deep links as a Stream. Register the handler as a Riverpod provider that exposes a Stream. The state and codeVerifier storage keys should be provider-namespaced (e.g., oauth_state_vipps, pkce_verifier_vipps) to support simultaneous flows if needed. Use Dart's crypto package for PKCE SHA-256 computation.

The handler should be initialized early in the app lifecycle (in main() or a top-level provider) to avoid missing deep links that arrive while the app is cold-starting. iOS Info.plist CFBundleURLSchemes and Android intent-filter must both be configured. Test on physical devices — deep link behavior on simulators can differ.

Testing Requirements

Unit tests using flutter_test. Test cases: (1) generateOAuthState returns a 64-character hex string; (2) generatePkceChallenge returns correct S256 challenge for a known verifier; (3) valid state match routes to correct provider; (4) state mismatch returns StateMismatchFailure; (5) Vipps callback URI parses code and state correctly; (6) BankID callback URI parses correctly; (7) error=access_denied returns OAuthUserDeniedFailure; (8) expired state (TTL exceeded) returns StateExpiredFailure; (9) state and codeVerifier are deleted from storage after validation regardless of outcome. Mock flutter_secure_storage in tests.

Component
Deep Link / OAuth Redirect Handler
infrastructure medium
Epic Risks (3)
high impact high prob dependency

Norway has multiple BankID broker providers (e.g., Signicat, Criipto, Nets) with different integration contracts, pricing, and WebView behavior. If the broker is not selected and contractually agreed before implementation begins, the BankIDProviderClient may need to be rewritten after initial build.

Mitigation & Contingency

Mitigation: Define a minimal broker interface abstraction (session initiation, WebView URL generation, assertion validation) before writing any provider-specific code. Confirm broker selection with Norse Digital Products before starting this epic.

Contingency: If the broker changes after implementation, the abstraction layer allows replacing the provider-specific implementation behind the same interface with a targeted rewrite rather than a full redesign.

high impact medium prob technical

Android deep link handling with custom URI schemes can conflict with existing app links (HTTPS-based) or fail silently on certain Android versions if the intent filter is misconfigured, causing OAuth callbacks to never reach the app and leaving users stranded on the Vipps or BankID page.

Mitigation & Contingency

Mitigation: Use HTTPS app links (Android App Links) rather than custom URI schemes where possible, as they are more reliable on modern Android. Test deep link receipt on Android 12+ explicitly during development, as this version changed intent flag requirements.

Contingency: Implement a polling fallback for Vipps (check auth status on app foreground) as a secondary callback mechanism if deep link receipt fails on specific Android configurations.

medium impact medium prob dependency

Vipps Login has a separate test environment (mt2.vipps.no) that requires distinct test merchant credentials which must be applied for separately. If test credentials are delayed, integration testing of the VippsApiClient cannot proceed, blocking the entire authentication flow.

Mitigation & Contingency

Mitigation: Apply for Vipps test merchant credentials at the start of the project sprint, not when implementation begins. Use Vipps' publicly documented mock token responses for unit tests to decouple unit testing from live credentials.

Contingency: Implement the VippsApiClient with full mock injection support so all service-layer tests can run against a stub client while waiting for official test credentials.