2026-03-26 13:00:00+00:00

Managing user identity in modern healthcare applications is a complex task. Multi-tenant clinical portals must guarantee strict data separation between clinics, enforce granular Access Control Lists (ACLs), maintain audit trails for HIPAA compliance, and integrate with clinical EHR systems.

Traditional OAuth 2.0 identity providers (like standard AWS Cognito configurations) are excellent for basic SaaS architectures, but they lack native support for clinical context standards like SMART-on-FHIR. In clinical workflows, authorization must be tied not just to a user, but also to a clinical context (such as a specific patient ID or medical provider record).

This article details how to build a HIPAA-Compliant Multi-Tenant Identity System by federating AWS Cognito User Pools with AWS Medplum (a serverless, developer-friendly SMART-on-FHIR platform), mapping OIDC identity tokens to clinical context claims, and securing multi-tenant transactional routes.


๐Ÿ—๏ธ The Federatively Bound SMART-on-FHIR Identity Flow

To guarantee absolute security and a seamless login experience, we federate Cognito with the FHIR server. Users authenticate against a tenant-specific Cognito Client, which issues an ID Token. The middleware intercepts this token, extracts the tenant claims, and exchanges it for a secure SMART-on-FHIR session:

1. Client Application Initiates SMART Launch Flow with launch/patient scope 2. AWS Cognito Authenticates User Verifies 2FA via SMS Issues ID JWT Token 3. Exchange Handler Intercepts JWT Validates Claims and Tenant Context 4. Medplum FHIR Generates SMART Session Enforces Patient-Level Compartment ACLs 5. EHR / Audits Syncs FileMaker DB Archives Access Logs to CloudWatch

๐Ÿ”’ SMART-on-FHIR: The Core Clinical Context Concept

Under the standard OAuth 2.0 spec, a client requests authorization scopes like read:profile or write:records. In clinical environments, however, authorization scopes must restrict operations to patient compartments.

The SMART-on-FHIR standard extends OAuth 2.0 with clinical context parameters:

  1. User Scopes: user/Patient.read (allows the logged-in user to read any Patient resource they have access to).
  2. Patient Scopes: patient/Observation.read (allows reading Observation records, but strictly limited to the active patient context).
  3. Launch Scopes: launch/patient (signals to the OAuth server that during the login process, a patient context parameter must be selected and bound to the resulting access token).

By binding Cognito Identity claims to SMART scopes, we guarantee that a user logged into a patient portal can never execute cross-tenant queries or view clinical data belonging to other individuals.


๐Ÿ› ๏ธ The Complete Federated Identity Mapper

Below is the production-ready Node.js/TypeScript middleware implementation. It intercepts the Cognito User Pool ID Token, performs cryptographic signature checks, extracts tenant identifiers and clinical claims, and exchanges them for a validated Medplum FHIR Session:

// smart-identity-mapper.ts (Federated Cognito-to-FHIR Session Orchestrator)
import { CognitoJwtVerifier } from 'aws-jwt-verify';
import { MedplumClient } from '@medplum/core';
import { logger } from './logger.js';

// Initialize the AWS Cognito JWT Verifier (Verifies signature and expiration)
const jwtVerifier = CognitoJwtVerifier.create({
  userPoolId: process.env.COGNITO_USER_POOL_ID || 'us-east-1_xxxxx',
  tokenUse: 'id',
  clientId: process.env.COGNITO_CLIENT_ID || 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
});

interface CognitoClaims {
  sub: string;
  email: string;
  'custom:tenant_id': string;
  'custom:filemaker_id'?: string;
  'custom:medplum_patient_id'?: string;
}

export class SmartIdentityMapper {
  private medplum: MedplumClient;

  constructor(medplum: MedplumClient) {
    this.medplum = medplum;
  }

  /**
   * Translates a Cognito Identity JWT into an authenticated SMART-on-FHIR session
   */
  public async exchangeCognitoToken(idToken: string): Promise<string> {
    try {
      // 1. Cryptographically verify the Cognito ID Token
      const payload = await jwtVerifier.verify(idToken) as unknown as CognitoClaims;
      logger.info({ sub: payload.sub }, 'Cognito JWT Token successfully verified.');

      const tenantId = payload['custom:tenant_id'];
      const patientId = payload['custom:medplum_patient_id'];

      if (!tenantId) {
        throw new Error('Unauthorized: Cognito profile is missing tenant context.');
      }

      // 2. Perform Medplum External Authentication exchange
      // We pass the verified patient and tenant parameters as custom session properties
      const sessionData = await this.medplum.startExternalAuth({
        idToken: idToken,
        tenantId: tenantId,
        profile: {
          reference: `Patient/${patientId}`
        },
        scopes: [
          'openid',
          'profile',
          'launch/patient',
          `patient/Patient.read`,
          `patient/Observation.read`,
          `patient/DocumentReference.write`
        ]
      });

      logger.info(
        { patientId, tenantId, sessionId: sessionData.sessionId },
        '๐ŸŽ‰ SMART-on-FHIR Session established successfully.'
      );

      return sessionData.accessToken;
    } catch (err: any) {
      logger.error({ err }, 'Federated OAuth identity exchange failed.');
      throw new Error('Invalid or unverified clinical identity credentials.');
    }
  }

  /**
   * Resolves eventual consistency indexing lag during clinical ingestion workflows
   */
  public async fetchPatientWithRetry(patientId: string, maxAttempts = 3): Promise<any> {
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        const patient = await this.medplum.readResource('Patient', patientId);
        if (patient) return patient;
      } catch (err) {
        if (attempt === maxAttempts) throw err;
        
        logger.warn(
          { attempt, patientId }, 
          'Patient resource indexing lag detected. Retrying fetch...'
        );
        await new Promise(resolve => setTimeout(resolve, 2000));
      }
    }
  }
}

๐Ÿ›ก๏ธ Multi-Tenant Separation: HIPAA Security & TCPA Rules

To remain fully HIPAA-compliant, the identity middleware must enforce several critical network security practices:

1. The Isolation Compartment Pattern

FHIR resources must be partitioned strictly by tenant. We accomplish this in Medplum by assigning a Project (which acts as the logical tenant) and configuring Compartment-Based ACLs. A Patient resource has a native compartment identifier, and the security engine guarantees that a search like GET /fhir/R4/Observation automatically filters by the current patientโ€™s compartment.

2. TCPA-Compliant 2FA SMS Workflows

When opting patients into Two-Factor Authentication via SMS:

// 2fa-validator.ts (Fail-Safe Identity Recovery Pattern)
export function validateAndRecover2FA(patient: any): boolean {
  const is2FAEnabled = patient.extension?.find(
    (e: any) => e.url === 'https://axiobionics.com/2fa-status'
  )?.valueBoolean === true;

  if (!is2FAEnabled) return true; // 2FA not active; proceed safely

  const hasSmsNumber = patient.telecom?.some(
    (t: any) => t.system === 'sms' && t.value
  );

  if (is2FAEnabled && !hasSmsNumber) {
    // Fail-safe logic to prevent permanent user lockout
    logger.warn({ patientId: patient.id }, '2FA is active but phone number is missing. Triggering auto-recovery...');
    return false; // Signals login bot to auto-disable and bypass 2FA check
  }

  return true; // 2FA is valid and ready
}

๐Ÿ“ˆ Summary of Benefits

Connecting AWS Cognito to a serverless SMART-on-FHIR backend delivers an institutional-grade security framework:

  1. Compartmentalized Identity: Users are bound directly to their respective healthcare tenants, guaranteeing that data leaks between distinct clinics are mathematically impossible.
  2. Standardized Clinical Scopes: Access rules comply directly with the industry-standard SMART-on-FHIR framework, facilitating integration with standard hospital EHR systems.
  3. Resilient SMS Delivery: Enforcing strict E.164 verification formats and implementing fail-safe OTP recovery paths ensures clinical systems are always accessible during emergencies.
  4. Audit Immutability: Because each event is mapped back to the unique user OIDC identity, every search, write, and API call automatically creates a HIPAA-compliant log entry.

By leveraging AWS Cognito User Pools as your master OIDC provider and mapping user identities directly to SMART-on-FHIR scopes in AWS Medplum, you establish a resilient, highly secure foundation designed for clinical applications at scale.