Data Layer medium complexity mobile
1
Dependencies
1
Dependents
0
Entities
0
Integrations

Description

Persistence layer that reads and writes the selected organization ID and associated tenant metadata to both secure local storage (for offline resumption) and Supabase session metadata (for server-side RLS enforcement). Ensures the tenant context survives app restarts and is available to Supabase RLS policies from the moment of selection.

Feature: Organization Selection & Onboarding

tenant-session-store

Summaries

The Tenant Session Store is the persistence backbone of the organization selection experience, ensuring that a user's chosen organizational context is retained across app restarts, device sleeps, and network interruptions. Without this component, users would be forced to reselect their organization every time they open the app — a significant friction point that degrades daily active usage and increases abandonment rates in B2B mobile workflows. Beyond user convenience, this component also plays a direct role in backend security enforcement: by writing the selected organization ID into Supabase session metadata, it enables Row-Level Security policies on the server side to automatically filter all database queries to the correct tenant — making data isolation a built-in guarantee rather than an application-layer concern that developers must remember to enforce manually.

The Tenant Session Store is a medium-complexity infrastructure component with a direct dependency on the secure-storage-adapter. It introduces dual-write complexity — data must be persisted to both local secure storage and Supabase session metadata — which means testing must cover partial failure scenarios (e.g., local write succeeds but Supabase session update fails) and define a clear recovery strategy. This component is on the critical path for both app startup (context restoration) and logout/org-switch flows (context clearing), so regressions here can silently break RLS enforcement on the backend — a high-severity risk. Testing requirements include: org context round-trip (save + load), app restart persistence, org switch invalidation, logout clearing, and Supabase session metadata propagation verification with a backend engineer.

Coordinate session metadata field naming with the backend team before implementation to avoid RLS policy mismatches.

The Tenant Session Store implements a dual-persistence pattern: `saveSelectedOrg` writes the org ID and `TenantContext` struct to the secure-storage-adapter (likely Flutter Secure Storage or similar) AND calls `updateSupabaseSessionMetadata` to inject the org ID as a custom claim into the Supabase JWT session. This custom claim is what Supabase RLS policies read on every database query to enforce tenant isolation — making this component security-critical. On app startup, `loadSelectedOrg` and `loadTenantContext` are called to restore context before the route guard evaluates navigation. `clearSession` must atomically clear both local storage and Supabase session claims on logout or org switch to prevent stale-tenant data from leaking into subsequent sessions.

Use `flutter_secure_storage` for the local persistence layer and Supabase's `auth.updateUser` or session metadata APIs for server-side claim injection. Consider wrapping both writes in a try/catch with a rollback strategy if the Supabase update fails.

Responsibilities

  • Write selected org ID to secure local storage
  • Write org context metadata to Supabase session custom claims
  • Read persisted org ID on app startup for context restoration
  • Clear stored org context on logout or org switch
  • Provide typed access to stored tenant metadata

Interfaces

saveSelectedOrg(String orgId, TenantContext context) -> Future<void>
loadSelectedOrg() -> Future<String?>
loadTenantContext() -> Future<TenantContext?>
clearSession() -> Future<void>
updateSupabaseSessionMetadata(String orgId) -> Future<void>

Relationships

Dependencies (1)

Components this component depends on

Dependents (1)

Components that depend on this component

API Contract

View full contract →
REST /api/v1/tenant-sessions 5 endpoints
GET /api/v1/tenant-sessions
GET /api/v1/tenant-sessions/:id
POST /api/v1/tenant-sessions
PUT /api/v1/tenant-sessions/:id
DELETE /api/v1/tenant-sessions/:id