critical priority high complexity api pending api specialist Tier 1

Acceptance Criteria

initiateSession() POSTs to the Edge Function's /bankid/initiate endpoint and returns a typed BankIdSessionResponse containing loginUrl and sessionId
validateAssertion(assertionToken, sessionId) POSTs the assertion token to the Edge Function's /bankid/validate endpoint and returns a typed BankIdIdentityResult containing personnummer and displayName
pollSessionResult(sessionId) GETs the session status from /bankid/session/{sessionId} and returns BankIdSessionStatus (pending, completed, failed, expired)
All operations return Either<BankIdAuthFailure, T>; typed failures cover: SessionExpiredFailure, AssertionInvalidFailure, NetworkFailure, EdgeFunctionServerFailure
Client attaches the Supabase anonymous JWT (from Supabase Auth session) as Bearer token on all Edge Function calls
personnummer from the validated identity result is never logged or stored by this client — it is returned to the service layer immediately
Client correctly handles the case where the Edge Function returns an HTTP 200 with a typed error payload vs. a non-200 status
BankID assertion token is never validated client-side; client sends it verbatim to the Edge Function and trusts only the Edge Function response
Session poll implements a maximum retry count (configurable, default 30) with configurable interval (default 2 seconds) before returning SessionExpiredFailure
Client is registered as a Riverpod provider accepting BankIdEndpointConfig from task-002

Technical Requirements

frameworks
Flutter
Riverpod
Dart
Supabase Flutter SDK
apis
Supabase Edge Functions /bankid/initiate
Supabase Edge Functions /bankid/validate
Supabase Edge Functions /bankid/session/{id}
performance requirements
Session initiation must complete within 10 seconds
Assertion validation must complete within 15 seconds (server-to-server BankID call included)
Session polling interval default 2 seconds, configurable to avoid hammering the Edge Function
security requirements
National identity number (personnummer) never stored in app database in plaintext — encrypted at rest
PKCE prevents authorization code interception on mobile
BankID session tokens not persisted; only derived application JWT kept
GDPR: personnummer processing requires explicit DPA and lawful basis
Supabase anonymous JWT attached to all calls — Edge Function validates JWT claims server-side
Assertion token forwarded verbatim — never inspected, decoded, or cached client-side

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

This client is a thin HTTP adapter — all business logic lives in the service layer. The key invariant is that assertion validation never happens client-side. The client's job is to (1) get a login URL, (2) send the assertion token to the backend, and (3) retrieve the result. Session polling should be implemented as a Stream with a configurable pollInterval and maxAttempts, allowing the service layer to cancel the stream when the user navigates away.

Attach the Supabase JWT by reading from Supabase.instance.client.auth.currentSession — do not store the JWT separately. Use the same HTTP client interface as the Vipps client for testability. Define a BankIdSessionPoller helper class if the polling logic is complex enough to warrant isolation.

Testing Requirements

Unit tests using flutter_test with mock HTTP adapter. Test cases: (1) initiateSession returns correct BankIdSessionResponse with loginUrl and sessionId; (2) validateAssertion with valid assertionToken returns BankIdIdentityResult with personnummer and displayName; (3) validateAssertion with rejected assertion returns AssertionInvalidFailure; (4) pollSessionResult returns pending, completed, and failed states correctly; (5) poll exceeding max retries returns SessionExpiredFailure; (6) Edge Function returning HTTP 500 maps to EdgeFunctionServerFailure; (7) network timeout maps to NetworkFailure; (8) assert personnummer is not present in any log output (use log capture in test).

Component
BankID Provider Client
infrastructure high
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.