SMART on FHIR Example

This example shows how to use Descope Fine-Grained Authorization (FGA) to check patient access behind a SMART on FHIR server. A SMART scope such as patient/Observation.rs says that an app can read observations, but not which patients' observations. FGA adds a relationship check between the user and the patient on every request.

Before you start, register your FHIR server as a Resource with its SMART scopes, as described in Register Your FHIR Server as an API Resource.

How FGA Checks Patient Access in a SMART on FHIR Request

Your FHIR server runs the FGA check on every request for patient data, after it validates the access token and its SMART scopes. Descope issues the token, stores the relations, and answers the check.

SMART AppFHIR ServerDescope1. User signs in and approves scopes2. Access token with sub and SMART scopes3. GET /Patient/pt-alvarez with access token4. Validate token and check SMART scopes5. FGA check (sub, read_record, patient pt-alvarez)6. allowed true or falsealt[allowed]7. 200 with Patient resource[denied]7. 403 Forbidden

Select any step to see what happens.

The scope check confirms that the token allows this type of request. The FGA check confirms that this user has a relationship with this specific patient.

Set Up FGA in the Descope Console

Define the Relationships between Users and Patients

This schema models access to patient data. To add it in the Console:

  1. In the Descope Console, go to Authorization > FGA.
  2. On the Schema tab, click Start from Blank, or open your existing schema in the editor.
  3. Paste the schema into the editor and click Save.
model AuthZ 1.0

type user

type patient
  relation subject: user
  relation careteam: user
  relation attending: user

  permission read_record: subject | careteam | attending
  permission write_note: attending

The three relations describe how a user is connected to a patient:

  • subject: the patient themselves
  • careteam: a nurse or other member of the patient's care team
  • attending: the patient's attending physician

The two permissions are derived from these relations. read_record is granted by any of the three relations, and write_note is granted only by attending.

Saved patient schema in the FGA schema editor with the Permission Graphs view

After you save, the panel next to the editor shows two graphs. The Relations Graph shows which types connect to each other and through which relations. The Permission Graphs show which relations grant each permission, so you can confirm that only attending grants write_note.

To understand full schema syntax, see Defining a Schema. To manage a schema with the SDK, see Managing a Schema.

Create Relations between Users and Patients

A relation connects a user and a patient. To create a relation in the Console:

  1. Go to Authorization > FGA > Relations and click + Relation.
  2. Fill in the fields:
    • Resource Type: patient
    • Resource: the FHIR Patient ID, for example pt-alvarez
    • Relation: subject, careteam, or attending
    • Target Type: user
    • Target: the user's Descope user ID, for example U2abc123def456
  3. Click Create.

New Relation dialog with a patient resource and a user target

The examples in the rest of this guide use these relations:

  • Dr. Chen is attending on pt-alvarez
  • Nurse Patel is careteam on pt-alvarez
  • Maria Alvarez is subject on pt-alvarez

In production, create relations from your care team or admissions system with createRelations, or from IdP groups at sign-in with SSO group mapping.

Test and Enforce Patient Access

Test Access Rules with Assertions

Assertions verify that your schema and relations return the expected access decisions. Each assertion specifies whether a user should be allowed or denied permission for a patient.

  1. On the Relations tab, click Test to open Assertions.
  2. Click + Assertion and fill in Resource Type, Resource, Relation or Permission, Target Type, Target, and Ruling (Allow or Block).
  3. Click Run All to run every assertion.

New Assertion dialog with a Block ruling

Based on the example relations, here are the expected access results. In this scenario, pt-nguyen represents a patient with no assigned relations:

UserRead pt-alvarezWrite note to pt-alvarezRead pt-nguyen
Dr. ChenAllowAllowBlock
Nurse PatelAllowBlockBlock
Maria AlvarezAllowBlockBlock

Add an assertion for each result in the table, including every Block. A test set with only Allow assertions still passes when the schema grants too much access.

Design schemas in FGA Studio

FGA Studio is a visual designer for Descope AuthZ 1.0 schemas that lets you edit a schema as a graph or as code. Test your schema there with sample relations and assertions, then click Copy as FGA schema and paste it into the Console.

Check Patient Access in Your FHIR Server

Run the FGA check in your FHIR server for every request that reads or changes patient data. Read and search requests check read_record, while creating clinical notes checks write_note. This example schema has no permission for any other write, including PUT, PATCH, and DELETE, so deny those requests or add a permission for them to your schema.

The check needs two values from the request:

  • resource: the ID of the patient whose data the request reads or changes, such as pt-alvarez. Resolve it on the server from the /Patient/{id} path, the patient search parameter, the subject of a new resource, or, for a request such as GET /Observation/{id}, the patient reference stored on the resource. These values can be FHIR references such as Patient/pt-alvarez, so read the ID from the reference, and deny the request if it points to a resource type other than Patient. The ID must match the Resource field you entered when you created the relation.
  • target: the user's Descope user ID from the access token's sub claim. Read it only after you validate the token.

If either value does not match the stored relation, the check returns false. Log both values whenever a check fails.

Deny any request for which you cannot resolve a single patient, such as a search without a patient parameter. If a response can contain resources for more than one patient, for example through _include or _revinclude, run the check for every patient in the response.

With the Node SDK, the check for a read request looks like this:

const res = await descopeClient.management.fga.check([
  {
    resource: patientId, // the FHIR Patient ID, such as 'pt-alvarez'
    resourceType: 'patient',
    relation: 'read_record', // or 'write_note' when creating a clinical note
    target: userId, // the user ID from the access token's sub claim
    targetType: 'user',
  },
]);
const allowed = res.ok && res.data?.[0]?.allowed === true;

Perform checks in this order: validate the access token's signature and expiry, confirm that its aud claim includes the FHIR server Resource's Audience, verify the SMART scopes, and then run the FGA check. If the token uses patient/ scopes, the requested patient, every patient in the response, and any patient that is changed must match the token's patient claim, in addition to the FGA check. Deny the request if the check errors or times out.

The FGA check requires a management key. Store this key securely on your FHIR server only, never in client apps, and assign it only the necessary roles. For SDK setup and examples in other languages, see Checking Relations.

Was this helpful?

On this page