FHIR Complete

Authentication

SMART on FHIR authentication with ES256 JWT tokens

Overview

The system uses SMART on FHIR authentication with ES256 (Elliptic Curve) signed JWT tokens. All authentication flows go through the smart-auth Edge Function.

Authentication Flow

┌─────────────┐    ┌──────────────┐    ┌─────────────────┐
│  Login Form │───▶│ /authenticate│───▶│ Org Selection   │
│             │    │              │    │ (if multi-org)  │
└─────────────┘    └──────────────┘    └────────┬────────┘

                   ┌──────────────┐    ┌────────▼────────┐
                   │  Dashboard   │◀───│ /select-staff   │
                   │              │    │  -organization  │
                   └──────────────┘    └─────────────────┘

Staff Login

  1. POST /authenticate with email + password
  2. If user belongs to one org: token returned directly
  3. If user belongs to multiple orgs: returns org list
  4. POST /select-staff-organization with chosen org_id
  5. Returns JWT token scoped to that organization

Patient Login

  1. POST /authenticate-patient with email + password
  2. Similar multi-org flow via POST /select-organization
  3. Returns JWT with patient_id claim

JWT Token Structure

{
  "sub": "user-uuid",
  "email": "doctor@hospital.com",
  "roles": ["doctor"],
  "role": "doctor",
  "org_id": "organization-uuid",
  "org_name": "City Hospital",
  "practitioner": "practitioner-db-uuid",
  "practitioner_id": "fhir-resource-id",
  "practitioner_reference": "Practitioner/fhir-id",
  "privileges": ["view_patients", "manage_encounters", "..."],
  "is_national": false,
  "exp": 1705312800,
  "iat": 1705226400
}

Key Claims

ClaimDescription
subSupabase auth user UUID
rolesArray of roles in current org
rolePrimary role (deprecated, use roles)
org_idCurrent organization UUID
practitionerInternal UUID of practitioner record
practitioner_idFHIR resource_id of practitioner
practitioner_referenceFull FHIR reference (e.g., Practitioner/UNB_doctor_001)
privilegesThe privilege list resolved from the user's roles at login
is_nationalWhether org has cross-org access
patient_id(Patient tokens only) Patient resource_id

Using Auth Headers

All API requests require the JWT in the Authorization header:

Authorization: Bearer <jwt-token>

The fhir-api Edge Function validates the token on every request using validateSmartToken(), which:

  1. Verifies ES256 signature
  2. Checks expiration
  3. Extracts session claims (org_id, practitioner, roles, etc.)

Session Interface

interface UserSession {
  user_id: string;
  email: string;
  access_token: string;
  org_id: string;
  org_name: string;
  roles: string[];
  role: string;
  practitioner?: string;
  practitioner_id?: string;
  practitioner_reference?: string;
  privileges: string[];
  is_national: boolean;
  expires_at: number;
}

Multi-Organization Support

  • Users can belong to multiple organizations with different roles per org
  • Organization selection issues a new JWT scoped to the chosen org
  • The org_id claim scopes all data access via RLS policies
  • Users can switch orgs without re-entering credentials

Three Auth Code Paths

PathHandlerUsed By
/select-organizationindex.ts inlineTests + mobile app
/select-staff-organizationselect-staff-organization.tsStaff web GUI
Single-org auto-loginauthenticate.tsWhen user has exactly 1 org

All three return consistent roles[], privileges[], and JWT claims.

Token Signing

  • Algorithm: ES256 (ECDSA with P-256 curve)
  • Key type: Asymmetric (private key signs, public key verifies)
  • Storage: Private key in Supabase Edge Function environment variables
  • Library: jose (JavaScript Object Signing and Encryption)