Skip to main content

Patient Portal

The patient portal gives patients direct control over their consent records. It is available at /consent/portal after OAuth2 login.

Authenticationโ€‹

Patients authenticate via the OAuth2 login flow configured in ConsentSecurityConfig. Spring Security redirects unauthenticated requests to /login, authenticates via the configured SMART auth server, and returns the patient to the portal.

The patient ID is resolved from the OAuth2 principal:

  1. user.getName() โ€” the configured user-name-attribute from your OAuth2 client registration
  2. user.getAttribute("sub") โ€” fallback for providers that don't set a user-name-attribute

The patient ID is prefixed with Patient/ if not already present. If neither source yields a usable identity, the portal returns an error asking the patient to sign in again โ€” the sentinel Patient/unknown is never stored as a real patient ID.

Pagesโ€‹

Dashboard โ€” /consent/portalโ€‹

Shows all active consents for the authenticated patient:

  • Application or organisation the consent covers (actorReference)
  • Which FHIR resource types are shared (resourceClasses)
  • Which operations are permitted (permittedOperations letters)
  • Validity period
  • Last 10 audit events

Grant โ€” /consent/portal/grantโ€‹

Patients can grant access to a new application directly from the portal. They select:

  • The application name (stored as a Device/ reference)
  • Which FHIR resource types to share (from a safe whitelist of 14 standard R4 types)
  • An expiry date (optional)

SMART scope strings are derived automatically โ€” patients never see patient/Observation.rs.

Edit โ€” /consent/portal/edit/{id}โ€‹

Patients can narrow the scope or shorten the expiry period of an existing active consent. They cannot expand scope or extend the period beyond the original end date โ€” server-side validation enforces this regardless of what is submitted. To expand access, the patient must revoke the existing consent and grant a new one.

History โ€” /consent/portal/historyโ€‹

All consent records for the patient across all statuses (active, inactive, rejected), with the full IHE ATNA audit trail. Shows:

  • Permit and deny counts
  • Every consent decision (permit and deny), creation, update, and revocation event
  • Timestamp, resource type, outcome, and purpose of use for each audit event

The complete audit trail for a single consent record โ€” every decision, update, and revocation against that specific record.

Revoke โ€” /consent/portal/revoke/{id}โ€‹

Confirmation screen before revoking a consent. Shows what access will be removed and which application will lose access. After confirmation:

  • Consent status is set to inactive
  • The application loses access on its next FHIR request
  • The revocation is recorded in the audit trail permanently

Ownership is enforced โ€” patients can only revoke consents that belong to their own patient ID.

Securityโ€‹

The portal chain uses session-based authentication (CSRF enabled). The FHIR and REST API chains are stateless JWT โ€” they do not share the session.

All portal routes enforce ownership: consentService.findById(id).filter(r -> patientId.equals(r.getPatientId())). A patient cannot view, edit, revoke, or access any consent record that does not belong to their own patient ID. Mismatched ownership returns 404 (not 403) to avoid confirming that a record exists.

Server-side input validation is applied to all portal form submissions โ€” HTML maxlength attributes are enforced at the server, not only in the browser.


Next: Audit Trail โ†’