critical priority high complexity backend pending backend specialist Tier 1

Acceptance Criteria

Edge Function /bankid/validate accepts POST with JSON body { assertionToken: string, sessionId: string } and returns { personnummer: string, displayName: string } on success
Edge Function /bankid/initiate accepts POST and returns { loginUrl: string, sessionId: string } after creating a BankID session with the broker
Edge Function /bankid/session/{sessionId} accepts GET and returns { status: 'pending' | 'completed' | 'failed' | 'expired' }
Assertion token signature is validated against BankID's JWKS endpoint before any identity data is trusted
Token expiry (exp claim) is checked; expired tokens return HTTP 422 with code ASSERTION_EXPIRED
The function uses environment-specific BankID broker endpoints (test vs. production) via Deno environment variables
BankID broker API key / credentials are read from Deno.env — never hardcoded or committed
Input is validated before any database write or external API call; malformed input returns HTTP 400 with descriptive code
personnummer is returned in the response but never written to any Supabase table by this function — that is the calling service's responsibility
Function scopes operations to the organisation context extracted from the validated Supabase JWT claims
HTTP 200 is returned only for successful validation; all failure paths return appropriate 4xx/5xx with a typed error payload { code: string, message: string }
Function is deployed to both staging and production Supabase projects with distinct environment variables

Technical Requirements

frameworks
Supabase Edge Functions (Deno)
Deno standard library
apis
BankID OIDC / REST API (broker server-to-server)
BankID JWKS endpoint for signature verification
Supabase Admin SDK (Deno) for JWT validation
performance requirements
Server-to-server BankID broker call must complete within 12 seconds (BankID SLA)
JWKS keys must be cached in-memory for the function lifetime to avoid re-fetching on every call
Function cold start should not exceed 3 seconds
security requirements
Service role key never distributed to mobile clients — only available server-side in function environment
Function input validated before any database write
Functions scoped by organisation context from JWT claims
National identity number (personnummer) never stored in app database in plaintext
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
All secrets in Deno.env — never in source code or committed config files
Validate the Supabase JWT on every request using the Supabase Admin SDK before processing

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Structure the Edge Function as three route handlers in a single function entry point (or three separate Edge Functions if Supabase routing is cleaner). Use the jose Deno library for JWT signature verification against the BankID JWKS endpoint. Cache the JWKS document using a module-level Map to avoid re-fetching on every invocation. The sessionId is a server-generated UUID stored in a short-lived Supabase table (e.g., bankid_sessions with a 10-minute TTL enforced by created_at + pg_cron or Postgres TTL extension).

The validate endpoint deletes the session row after successful validation to prevent replay. Use Deno's built-in crypto for any HMAC operations. Never use console.log with the assertion token or personnummer — use structured logging with redaction.

Testing Requirements

Deno unit tests for: (1) valid assertion token returns correct identity payload; (2) expired token returns ASSERTION_EXPIRED error; (3) tampered/invalid signature returns ASSERTION_INVALID error; (4) malformed request body returns HTTP 400; (5) missing or invalid Supabase JWT returns HTTP 401; (6) BankID broker returning an error maps to typed EdgeFunctionServerFailure response. Use Deno test mocking for the BankID broker HTTP calls. Integration test in Supabase local development environment using supabase functions serve with test BankID credentials. Document test environment BankID credentials in CI secrets.

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.