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:
user.getName()โ the configured user-name-attribute from your OAuth2 client registrationuser.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 (
permittedOperationsletters) - 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
Consent detail โ /consent/portal/history/{id}โ
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
statusis set toinactive - 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 โ