FHIR Oauth


This is a sample application that demonstrates how to use the InterSystems IRIS for Health FHIR Repository to build a FHIR Repository with OAuth2 authorization, the FHIR endpoint will be the resource server and Google OpenId will be the authorization server.
Prerequisites
- Docker
- Git
- Google Cloud Platform account
Installation
Setup Google Cloud Platform
This part is inspired by the article Adding Google Social Login into InterSystems Management Portal from yurimarx Marx in the InterSystems Community.
Create a new project in Google Cloud Platform
On the header click Select a project:
- Click the button NEW PROJECT:
- Create a sample project for this article called InterSystemsIRIS and click the button CREATE:
- Go to the Header again and select the created project InterSystemsIRIS hyperlink in the table:
- Now the selected project is the working one:
- In the header look for credentials on the Search field and choose API credentials (third option for this image):
- On the top of the screen, click the + CREATE CREDENTIALS button and select OAuth 2.0 Client ID option:
- Now click CONFIGURE CONSENT SCREEN:
- Choose External (any person who has Gmail is able to use it) and click the CREATE button:
- In Edit app registration, complete the field values as follow: App Information (use your email for user support email):

- For Authorized domains, it is not necessary to set anything because this sample will use localhost. Set the developer contact information with your email and click the SAVE AND CONTINUE button:

- Click ADD OR REMOVE SCOPES and select the following scopes, scroll the dialog, and click the UPDATE button:

- Include your email into the Test users list (using the +ADD USERS button) and click the SAVE AND CONTINUE button:

- The wizard shows you the Summary of the filled fields. Scroll the screen and click the BACK TO DASHBOARD button.
- Now, it is time to configure the credentials for this new project. Select the option Credentials:
- Click the + CREATE CREDENTIALS button and select OAuth client ID option:
- Select Web application option and complete the field values as follow:

We will be using postman for the demo, but if you want to use the sample application, you will need to add the following redirect URIs, same goes for the JavaScript origins.
- Click the CREATE button and copy the Client ID and Client Secret values:

You are done with the Google Cloud Platform configuration.
Setup the sample application
- Clone this repository:
git clone https://github.com/grongierisc/iris-oauth-fhir
- Build the docker image:
docker-compose build
- Set Client Id an Client Secret from the last part of (Setup Google Cloud Platform) in a new file called
secret.jsoninmisc/authfolder, you can use thesecret.json.templateas a template.
{
"web": {
"client_id": "xxxx",
"project_id": "intersystems-iris-fhir",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v3/certs",
"client_secret": "xxxx"
},
"other" : {
"issuer" : "accounts.google.com"
}
}
- Run the docker image:
docker-compose up -d
Test it with Postman
The endpoint is httsp://localhost:4443/fhir/r4/.
Configure Postman to use the self-signed certificate, see Postman documentation.
Create a new request in Postman and go to the Authorization tab. Select OAuth 2.0 as the type :

- On the Configure New Token dialog, set the following values:
The access url token is : https://accounts.google.com/o/oauth2/token
Scopes is : openid
Client Id and Client Secret are the one you got from the Google Cloud Platform.

- Click the Request Token button and you will be redirected to the Google login page:


- Make use of the token to get the patient list:

- Select in Token type, ID Token and click the Use Token button:

- You will get the patient list:

What journey, hope you enjoyed it.
More to come, stay tuned. We will be dealing with kubernetes and the FHIR repository in the next part.
Comments
Thanks and fixed.
BTW, now the new version of this project is in python :
import time
import os
import json
import iris
from FhirInteraction import Interaction, Strategy, OAuthInteraction
from google.oauth2 import id_token
from google.auth.transport import requests
import requests as rq
# The following is an example of a custom OAuthInteraction class thatclassCustomOAuthInteraction(OAuthInteraction):
client_id = None
last_time_verified = None
time_interval = 5defclear_instance(self):
self.token_string = None
self.oauth_client = None
self.base_url = None
self.username = None
self.token_obj = None
self.scopes = None
self.verify_search_results = Nonedefset_instance(self, token:str,oauth_client:str,base_url:str,username:str):
self.clear_instance()
ifnot token ornot oauth_client:
# the token or oauth client is not set, skip the verificationreturn
global_time = iris.gref('^FHIR.OAuth2.Time')
if global_time[token[0:50]]:
self.last_time_verified = global_time[token[0:50]]
if self.last_time_verified and (time.time() - self.last_time_verified) < self.time_interval:
# the token was verified less than 5 seconds ago, skip the verificationreturn
self.token_string = token
self.oauth_client = oauth_client
self.base_url = base_url
self.username = username
# try to set the client idtry:
# first get the var env GOOGLE_CLIENT_ID is not set then None
self.client_id = os.environ.get('GOOGLE_CLIENT_ID')
# if not set, then by the secret.json fileifnot self.client_id:
with open(os.environ.get('ISC_OAUTH_SECRET_PATH'),encoding='utf-8') as f:
data = json.load(f)
self.client_id = data['web']['client_id']
except FileNotFoundError:
passtry:
self.verify_token(token)
except Exception as e:
self.clear_instance()
raise e
# token is valid, set the last time verified to now
global_time[token[0:50]]=time.time()
defverify_token(self,token:str):# check if the token is an access token or an id tokenif token.startswith('ya29.'):
self.verify_access_token(token)
else:
self.verify_id_token(token)
defverify_access_token(self,token:str):# verify the access token is valid# get with a timeout of 5 seconds
response = rq.get(f"https://www.googleapis.com/oauth2/v3/tokeninfo?access_token={token}",timeout=5)
try:
response.raise_for_status()
except rq.exceptions.HTTPError as e:
# the token is not validraise e
defverify_id_token(self,token:str):# Verify the token and get the user info
idinfo = id_token.verify_oauth2_token(token, requests.Request(), self.client_id)
if idinfo['iss'] notin ['accounts.google.com', 'https://accounts.google.com']:
raise ValueError('Wrong issuer.')
defget_introspection(self)->dict:return {}
defget_user_info(self,basic_auth_username:str,basic_auth_roles:str)->dict:return {"Username":basic_auth_username,"Roles":basic_auth_roles}
defverify_resource_id_request(self,resource_type:str,resource_id:str,required_privilege:str):passdefverify_resource_content(self,resource_dict:dict,required_privilege:str,allow_shared_resource:bool):passdefverify_history_instance_response(self,resource_type:str,resource_dict:dict,required_privilege:str):passdefverify_delete_request(self,resource_type:str,resource_id:str,required_privilege:str):passdefverify_search_request(self,
resource_type:str,
compartment_resource_type:str,
compartment_resource_id:str,
parameters:'iris.HS.FHIRServer.API.Data.QueryParameters',
required_privilege:str):passdefverify_system_level_request(self):passclassCustomStrategy(Strategy):defon_get_capability_statement(self, capability_statement):# Example : del resources Account
capability_statement['rest'][0]['resource'] = [resource for resource in capability_statement['rest'][0]['resource'] if resource['type'] != 'Account']
return capability_statement
classCustomInteraction(Interaction):defon_before_request(self, fhir_service, fhir_request, body, timeout):#Extract the user and roles for this request#so consent can be evaluated.
self.requesting_user = fhir_request.Username
self.requesting_roles = fhir_request.Roles
defon_after_request(self, fhir_service, fhir_request, fhir_response, body):#Clear the user and roles between requests.
self.requesting_user = ""
self.requesting_roles = ""defpost_process_read(self, fhir_object):#Evaluate consent based on the resource and user/roles.#Returning 0 indicates this resource shouldn't be displayed - a 404 Not Found#will be returned to the user.return self.consent(fhir_object['resourceType'],
self.requesting_user,
self.requesting_roles)
defpost_process_search(self, rs, resource_type):#Iterate through each resource in the search set and evaluate#consent based on the resource and user/roles.#Each row marked as deleted and saved will be excluded from the Bundle.
rs._SetIterator(0)
while rs._Next():
ifnot self.consent(rs.ResourceType,
self.requesting_user,
self.requesting_roles):
#Mark the row as deleted and save it.
rs.MarkAsDeleted()
rs._SaveRow()
defconsent(self, resource_type, user, roles):#Example consent logic - only allow users with the role '%All' to see#Observation resources.if resource_type == 'Observation':
if'%All'in roles:
returnTrueelse:
returnFalseelse:
returnTrueBased on https://community.intersystems.com/post/iris-fhir-python-strategy
yes, it is, i fixed the tutorial, thanks
.png)