Skip to main content

Patient Portal Service — Security Model

Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 13 security-compliance-tenancy · 02 DDD

1. Authentication

MechanismDetails
ProtocolOIDC / OAuth2 with PKCE (no implicit grant)
Token issuerKeycloak — dedicated patient realm per tenant
MFA requirementMFA is mandatory for all portal accounts (mfaEnabled enforced at account activation)
MFA methodTOTP (Authenticator app) or SMS OTP
SessionJWT access token (15 min TTL) + refresh token (24h). Refresh rotates on use.
Token validationPortal BFF validates JWT signature + iss, aud, iat, exp claims on every request
ACR checkSensitive actions (export, account deletion) require acr >= 2 in JWT claims

2. Authorization — SMART on FHIR Scope Matrix

ScopeResource typeOperations permitted
patient/Patient.readPatientGET demographics, account info
patient/Patient.writePatientDemographics update request, proxy delegation
patient/Observation.readObservationLab results (policy-filtered)
patient/DiagnosticReport.readDiagnosticReportRadiology results (policy-filtered)
patient/MedicationRequest.readMedicationRequestMedication list
patient/MedicationRequest.writeMedicationRequestRefill request
patient/Immunization.readImmunizationImmunization records
patient/Condition.readConditionProblem list
patient/AllergyIntolerance.readAllergyIntoleranceAllergy list
patient/Appointment.readAppointmentAppointment list
patient/Appointment.writeAppointmentAppointment request, cancellation
patient/Coverage.readCoverageInsurance coverage
patient/ExplanationOfBenefit.readExplanationOfBenefitEOB read
patient/Communication.readCommunicationSecure messages
patient/DocumentReference.readDocumentReferenceDocuments

3. Proxy Access Enforcement

When a proxy session is active (proxy PortalAccount acting on behalf of a patient), the BFF enforces:

  1. The ProxyDelegation record must have status = active.
  2. ProxyDelegation.validFrom <= today <= validTo (if validTo is set).
  3. The requested resource type must be present in ProxyDelegation.scope.
  4. Proxy cannot exceed the permissions of the grantor patient.
  5. All access events are recorded with actingAsProxy = true and proxyDelegationId.

4. Data Classification and Encryption

ClassificationApplies toAt-rest encryptionIn-transit
PHIportal_accounts, proxy_delegations, demographics_update_requests, portal_access_eventsPostgreSQL TDE (AES-256)TLS 1.3
PIIportal_accounts.idp_subject, ip_hashAES-256 at restTLS 1.3
Operationalexport_jobs, outboxAES-256 at restTLS 1.3
No PHICached FHIR read projectionsRedis: encrypted at restTLS 1.3

ip_hash is a SHA-256 hash of the client IP. The raw IP is never stored. Push notification tokens are stored in Redis with TTL; never in Postgres.


5. Result Release Policy Enforcement

Lab and radiology results are only surfaced to patients when the upstream service marks them as patient-visible. The BFF passes ?releasePolicy=patient-visible in all calls to laboratory-service and radiology-service. Results with releasePolicy=clinician_release or timed (time-lock not expired) are never included in portal responses.


6. PHI Boundary Rules

RuleDescription
No PHI in push notificationsPush payloads contain only notification type + deep-link URL
No PHI in AI promptsPortal navigation assistant prompts contain no patient identifiers or clinical data
No PHI in logsRequest logging redacts Authorization header, patient ID body fields, and result content
No cross-tenant dataRLS policy tenant_id = current_setting('app.tenant_id') enforced on all tables
Proxy scope isolationProxy cannot read resource types not included in their DelegationScope

7. RBAC: Portal-Specific Roles

RolePermissions
portal:patientAll patient/* scopes above; own data only
portal:proxyScopes subset as defined by ProxyDelegation.scope
portal:adminSuspend/reinstate accounts, review demographics update requests

8. Audit Events

All the following actions produce immutable PortalAccessEvent records (local) and are forwarded to audit-service via NATS:

EventAudit fields
LoginaccountId, patientId, mfaUsed, ipHash
Record viewedaccountId, resourceType, resourceId, actingAsProxy
Result viewedaccountId, observationId/reportId, actingAsProxy
Appointment requestedaccountId, requestId
Demographics update submittedaccountId, requestId
Proxy delegation granted/revokeddelegationId, grantorPatientId, actorId
Export requestedaccountId, exportJobId
Account suspended/reinstatedaccountId, actorId

9. GDPR / Data Rights Participation

RightMechanism
Right to accessFHIR export endpoint (POST /v1/portal/export) provides full PHR Bundle
Right to erasureAccount deletion request routes to registration-service for full data removal; portal tables purged after retention period
Right to portabilityFHIR Bundle export in application/fhir+ndjson format
Data residencyAll PHI remains in the tenant-designated region; no cross-border transfer

10. Threat Model Summary

ThreatMitigation
JWT token theftShort-lived access tokens (15 min); MFA required; refresh token rotation
Account takeoverMFA mandatory; anomalous login patterns trigger suspension (future)
Proxy scope escalationServer-side scope enforcement on every request; delegation checked against DB
FHIR result leakageRelease policy check enforced server-side; cached separately from unreleased data
PHI in AI promptsPrompt construction rules enforced at code level; validated in code review
Tenant data leakagePostgreSQL RLS on all tables; app.tenant_id session variable set per request