Home Applications iris-fhir-oauth-demo

iris-fhir-oauth-demo

Community Project
This project is maintained by its author and is not officially supported by InterSystems. For technical support, please contact the project developer.
0
0 reviews
0
Awards
2
Views
0
IPM installs
0
0
Details
Releases (1)
Reviews
Issues
Videos (1)
OAuth and SMART-on-FHIR integration between Auth0 and InterSystems IRIS FHIR, including patient-level authorization and Python examples.

What's new in this version

Initial Release

OAuth with Auth0 and InterSystems IRIS FHIR Server

A step-by-step guide demonstrating how to configure Auth0 as an OAuth/OpenID Connect provider for an InterSystems IRIS FHIR server, and how to consume protected FHIR resources from a Python application.

This repository accompanies the video walkthrough and serves as a reference implementation.


Architecture

┌───────────────┐
│ Python Client │
└───────┬───────┘
        │
        │ Login
        ▼
┌───────────────┐
│    Auth0      │
└───────┬───────┘
        │
        │ Access Token
        ▼
┌───────────────┐
│ IRIS FHIR API │
└───────────────┘

The flow is:

  1. The user logs in via Auth0.
  2. Auth0 issues an access token.
  3. The Python application sends the access token to the IRIS FHIR server.
  4. IRIS validates the token.
  5. IRIS returns FHIR resources according to the user’s scopes and token claims.

Features

  • OAuth 2.0 Authorization Code Flow
  • OpenID Connect authentication
  • Auth0 integration
  • InterSystems IRIS FHIR server integration
  • SMART-on-FHIR style scopes
  • Patient-level authorization
  • Auth0 Actions
  • Custom JWT claims

Prerequisites

Software

  • Python 3.9+
  • InterSystems IRIS for Health
  • Auth0 account
  • uv package manager

Python Dependencies

Install the dependencies:

uv sync

For Windows users, you may also need:

uv pip install python-certifi-win32

This helps Python trust locally installed certificates.


Step 1 - Create an Auth0 Account

Create an Auth0 account:

https://auth0.com

After logging in, create a new application:

Applications
→ Create Application

Choose:

Regular Web Application

Step 2 - Configure the Auth0 Application

In the Auth0 application settings, configure the following URLs.

Allowed Callback URLs

http://localhost:3000/callback

Allowed Logout URLs

http://localhost:3000

Allowed Web Origins

http://localhost:3000

Save the application settings.


Step 3 - Create a Test User

Navigate to:

User Management
→ Users
→ Create User

Create a test user.

Example:

Email:
oauthUser@example.com

Password: Password123!


Step 4 - Configure the Python Application

Create a .env file in the project root.

Example:

AUTH0_DOMAIN=your-domain.us.auth0.com
AUTH0_CLIENT_ID=your-client-id
AUTH0_CLIENT_SECRET=your-client-secret
AUTH0_SECRET=your-random-secret
APP_BASE_URL=http://localhost:3000

Do not commit your real .env file to GitHub.

Use .env.example for placeholder values instead.


Step 5 - Run the Sample Python Application

Start the application:

uv run python start.py

Open:

http://localhost:3000

You should be redirected to Auth0.

After login, you should be redirected back to your Python application.

At this stage, the goal is only to confirm that the basic Auth0 login flow works.


Step 6 - Configure OAuth on IRIS

Configure your IRIS FHIR server to trust Auth0 as the OAuth/OpenID Connect provider.

You will need the Auth0 issuer.

Issuer

https://YOUR_AUTH0_DOMAIN/

Example:

https://dev-abc123.us.auth0.com/

In this demo, IRIS only needs the Auth0 issuer endpoint. IRIS uses the issuer information to validate tokens issued by your Auth0 tenant.


Step 7 - Create an Auth0 API

In Auth0, navigate to:

Applications
→ APIs
→ Create API

Create an API that represents your IRIS FHIR server.

Example:

Name:
IRIS FHIR

Identifier: https://localhost:8443/csp/healthshare/demo/fhir/r4

The API identifier is important because it is used as the audience when requesting an access token.


Step 8 - Add Initial Broad FHIR Scope

In the first part of the demo, broad access is used to confirm that the OAuth flow works end-to-end.

Inside the Auth0 API, add the following permission:

user/*.*

This is a broad SMART-on-FHIR style scope.

For the initial demo, the Python application requests:

openid profile email user/*.*

This allows you to verify that:

  1. Auth0 login works.
  2. The Python application receives an access token.
  3. The access token is accepted by the IRIS FHIR server.
  4. The Python application can call the FHIR API.

Later, this broad scope is replaced with more restrictive patient-level scopes.


Step 9 - Request the Correct Scope and Audience

Modify the Auth0 configuration in the Python application to include both the scope and audience.

Example:

def auth0():
    return ServerClient(
        ...
        authorization_params={
            "scope": "openid profile email user/*.*",
            "audience": "https://localhost:8443/csp/healthshare/demo/fhir/r4"
        },
        ...
    )

The scope controls what permissions the token requests.

The audience tells Auth0 which API the token is intended for.

The audience should match the Auth0 API identifier created earlier.


Step 10 - Retrieve an Access Token

After login, retrieve the access token:

access_token = await auth0().get_access_token(
    store_options={"request": request}
)

This token is sent to the IRIS FHIR server in the Authorization header.


Step 11 - Call the FHIR Server

Example request:

import requests

headers = { "Authorization": f"Bearer {access_token}", "Accept": "application/fhir+json" }

response = requests.get( "https://localhost:8443/csp/healthshare/demo/fhir/r4/Patient/2", headers=headers, verify="intersystems.crt" )

print(response.status_code) print(response.json())

The verify="intersystems.crt" argument tells Python to trust the certificate used by the IRIS FHIR server.

If the configuration is correct, the FHIR server should return the requested Patient resource.


Step 12 - Configure Patient-Level Access

The next section demonstrates how to restrict the user to a specific patient.

In this part of the demo, the broad scope:

user/*.*

is replaced with more specific patient-level scopes:

patient/Patient.r
patient/Observation.r

These scopes mean:

patient/Patient.r

Read access to the Patient resource tied to the patient context in the token.

patient/Observation.r

Read access to Observation resources tied to the patient context in the token.


Step 13 - Add Patient-Level Scopes to the Auth0 API

Inside the Auth0 API permissions, add:

patient/Patient.r
patient/Observation.r

Your API permissions may now include:

user/*.*
patient/Patient.r
patient/Observation.r

For the restricted-access demo, update the Python application to request only the patient-level scopes:

def auth0():
    return ServerClient(
        ...
        authorization_params={
            "scope": "openid profile email patient/Patient.r patient/Observation.r",
            "audience": "https://localhost:8443/csp/healthshare/demo/fhir/r4"
        },
        ...
    )

This changes the token from broad user-level access to restricted patient-level access.


Step 14 - Add Patient Metadata to the Auth0 User

Navigate to:

User Management
→ Users
→ Select User

Add user metadata.

Example:

{
  "patient": "2"
}

This metadata tells Auth0 that this user should be associated with Patient 2.

Auth0 does not automatically know which FHIR Patient resource belongs to the user. The mapping is provided through user metadata.


Step 15 - Create an Auth0 Action

Navigate to:

Actions
→ Triggers
→ Post Login

Create a new Post Login Action.

Example:

exports.onExecutePostLogin = async (event, api) => {
  const patientRef = event.user.user_metadata?.patient;

if (patientRef) { api.accessToken.setCustomClaim("patient", patientRef); } };

Deploy the Action.

This Action copies the patient value from the Auth0 user’s metadata into the access token.

The variable name patientRef is used in the Action code for readability, but the actual claim added to the access token is named:

patient

The claim name is important. In this demo, IRIS uses the patient claim, together with the patient-level scopes, to enforce access to resources tied to that patient.


Step 16 - Add the Action to the Login Flow

Navigate to:

Actions
→ Triggers
→ Post Login

Drag the Action into the Post Login flow.

Click:

Apply

The Action will now run after the user logs in.

Log out and log in again to get a fresh access token containing the new claim.


Step 17 - Verify JWT Claims

Install python-jose:

uv add python-jose

Read the token claims:

from jose import jwt

claims = jwt.get_unverified_claims(access_token)

print(claims)

Example output:

{
  "patient": "2",
  "scope": "openid profile email patient/Patient.r patient/Observation.r"
}

This confirms that Auth0 has added the patient context to the access token.

Note: get_unverified_claims() is useful for debugging and demos, but production applications should validate the JWT signature, issuer, audience, and expiry before trusting token claims.


Step 18 - Verify Patient-Level Authorization

Retrieve Patient 2:

response = requests.get(
    f"{FHIR_BASE_URL}/Patient/2",
    headers=headers,
    verify="intersystems.crt"
)

print(response.status_code)

Expected:

200 OK

Retrieve Patient 3:

response = requests.get(
    f"{FHIR_BASE_URL}/Patient/3",
    headers=headers,
    verify="intersystems.crt"
)

print(response.status_code)

Expected:

403 Forbidden

or an equivalent authorization error.

This demonstrates that the token contains patient context and that the FHIR server enforces access restrictions.


Troubleshooting

SSL Certificate Errors

If you encounter SSL certificate errors, install:

uv pip install python-certifi-win32

or explicitly specify the IRIS certificate:

verify="intersystems.crt"

Invalid Audience

Verify that the Auth0 API identifier matches the audience used by the Python application.

Auth0 API Identifier:

https://localhost:8443/csp/healthshare/demo/fhir/r4

Python audience:

"audience": "https://localhost:8443/csp/healthshare/demo/fhir/r4"

These values should match.


Missing Claims

Verify that:

  • User metadata exists.
  • The Auth0 Action is deployed.
  • The Auth0 Action is attached to the Post Login flow.
  • You logged out and logged in again after deploying the Action.

Missing Scopes

Verify that the required scopes exist in:

Applications
→ APIs
→ IRIS FHIR
→ Permissions

For the broad-access demo:

user/*.*

For the restricted patient-access demo:

patient/Patient.r
patient/Observation.r

Also verify that the Python application is requesting the correct scopes.


Patient Restriction Not Working

Check that the token contains:

{
  "patient": "2"
}

Also check that the Python application is requesting patient-level scopes, not the broad user/*.* scope.

For patient-level testing, the scope should be similar to:

openid profile email patient/Patient.r patient/Observation.r

not:

openid profile email user/*.*

Security Notes

This repository is intended as a learning and demonstration project.

Do not commit:

  • Client secrets
  • Access tokens
  • Refresh tokens
  • Production certificates
  • Private keys
  • Real patient data
  • Internal URLs
  • Your real .env file

Use .env.example for placeholder configuration values.

For production deployments:

  • Validate JWTs properly.
  • Use HTTPS.
  • Store secrets securely.
  • Enforce authorization on the server side.
  • Do not rely only on client-side filtering.
Version
1.0.024 Aug, 2026
Category
Template
Works with
InterSystems IRISInterSystems FHIR
First published
24 Aug, 2026
Last edited
24 Aug, 2026