Initial Release
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.
┌───────────────┐
│ Python Client │
└───────┬───────┘
│
│ Login
▼
┌───────────────┐
│ Auth0 │
└───────┬───────┘
│
│ Access Token
▼
┌───────────────┐
│ IRIS FHIR API │
└───────────────┘
The flow is:
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.
Create an Auth0 account:
https://auth0.com
After logging in, create a new application:
Applications
→ Create Application
Choose:
Regular Web Application
In the Auth0 application settings, configure the following URLs.
http://localhost:3000/callback
http://localhost:3000
http://localhost:3000
Save the application settings.
Navigate to:
User Management
→ Users
→ Create User
Create a test user.
Example:
Email: oauthUser@example.com
Password: Password123!
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.
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.
Configure your IRIS FHIR server to trust Auth0 as the OAuth/OpenID Connect provider.
You will need the Auth0 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.
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.
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:
Later, this broad scope is replaced with more restrictive patient-level scopes.
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.
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.
Example request:
import requestsheaders = { "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.
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.
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.
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.
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.
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.
Install python-jose:
uv add python-jose
Read the token claims:
from jose import jwtclaims = 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.
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.
If you encounter SSL certificate errors, install:
uv pip install python-certifi-win32
or explicitly specify the IRIS certificate:
verify="intersystems.crt"
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.
Verify that:
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.
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/*.*
This repository is intended as a learning and demonstration project.
Do not commit:
.env fileUse .env.example for placeholder configuration values.
For production deployments: