critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

A sealed class or sealed hierarchy AuthFailure (or equivalent Dart pattern) is defined covering all failure categories
OAuth 2.0 error codes are represented as named variants: invalidGrant, accessDenied, invalidClient, invalidScope, serverError, and a generic oauthError(String code) catch-all
BankID-specific error variants are defined: sessionExpired, assertionInvalid, userCancelled, identityMismatch, bankIdUnavailable
Vipps-specific variants are defined: vippsUserCancelled, vippsLoginUnavailable, vippsNinRetrievalFailed
Network error variants are defined: networkTimeout, noInternetConnection, sslError
Environment/config error variants are defined: missingClientId, missingRedirectUri, invalidEnvironmentConfig
Each error variant carries a human-readable message property suitable for display and a technical detail property for logging
No variant exposes raw NIN (personnummer) or phone numbers in its message or detail fields
The error hierarchy is in a shared location importable by both VippsApiClient and BankIdApiClient without circular imports
Dart documentation comments (///) are present on each sealed class variant explaining when it is emitted
A Result<T, AuthFailure> type alias or Either equivalent is defined for use as the return type of all auth service methods

Technical Requirements

frameworks
Flutter
Dart
apis
Vipps Login API (OAuth 2.0)
BankID OIDC / REST API
data models
device_token
performance requirements
Error model instantiation must be allocation-cheap — use const constructors for all fixed variants
security requirements
No error variant may store or expose NIN (personnummer) in plaintext — BankID session tokens not persisted beyond authentication
Error detail strings must be safe to log without leaking PII; sanitise before populating detail field
PKCE error variants must not expose code_verifier or code_challenge values
Error models must never be serialised to local storage — they are in-memory transport types only

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart's sealed class feature (available since Dart 3.0) for exhaustive pattern matching at compile time. Define the hierarchy in lib/src/auth/errors/auth_failure.dart (or equivalent path under the auth domain). Example structure: sealed class AuthFailure { const AuthFailure(); } followed by final class NetworkTimeout extends AuthFailure { const NetworkTimeout(); } and so on. For variants that carry contextual data (e.g., oauthError carrying the raw code string), use a named parameter in the constructor.

For the Result type, either use a package like fpdart (Either) or define a minimal sealed Result in the same domain folder — do not pull in a new dependency if the project doesn't already use one; check existing dependencies first. Ensure the error type file has no imports from Flutter (dart:ui, package:flutter) — it must be a pure Dart file so it can be used in Edge Function Dart code if needed. Document the mapping from Vipps OAuth error response fields (error, error_description) to AuthFailure variants in a code comment for future maintainers.

Testing Requirements

Write Dart unit tests covering: (1) each sealed class variant can be instantiated with expected properties, (2) pattern matching (switch/when) over AuthFailure is exhaustive — verified by adding a new variant and confirming the compiler error, (3) message and detail fields do not contain NIN or phone number values in any variant's default factory, (4) const constructors are used for fixed variants (verify with identical() check). No widget tests needed for this task — pure model layer. Run dart analyze with zero warnings on the new files.

Component
Vipps API Client
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.