Authorization Code Authentication using Python

To work with the FHIR API you first need to authenticate the application and the user via SMART-on-FHIR OAuth. The process happens in a few simple steps:

  • Set up and publish a SMART-on-FHIR client.
  • Get an authorization code.
  • Trade the authorization code for a token.
  • Use the token to access the FHIR API.

Once you complete these steps, you’ll be able to execute the actions made by users of your SMART-on-FHIR application. We’ve provided more detail and some sample Python code to show you how this works.

Set up and publish a SMART on FHIR client

A detailed step-by-step guide is here in the App Registration section.

Get an authorization code

Once the app is published, copy the client ID for the app which can be found on the Apps list on the Developer Dashboard.

Dev Platform App Dashboard

Dev Platform App Dashboard

📘

Note

  • Secret Expiring Soon: This icon indicates that an app's secret will expire within the next 15 days.
  • Secret Expired: This icon indicates that an app's secret has already expired.

When the app is launched by the user, to get an authorization code, the user clicks a link that triggers a GET request to the following address, with your app’s client ID and other info filled in instead of the blanks. (We’ve got some sample code you can use to do this further down in this guide.)

https://auth-api.login.greenwayhealth.com/oauth2/auth-code?response_type=code&client_id=___&scope=___&redirect_uri=___&aud=https://fhir-api.fhirprod.aws.greenwayhealth.com/fhir/R4/___&code_challenge=YOUR_CODE_CHALLENGE&code_challenge_method=S256

The address is our Authorization server. You can always obtain this address by querying the FHIR server’s /. well-known/smart-configuration or /metadata endpoint in any API tool or by using the following cURL command.

Greenway Health’s FHIR servers return the /.well-known endpoints as a format most web browsers will not recognize. Instead, please query this end point via API call to obtain the details.

https://fhir-api.fhirprod.aws.greenwayhealth.com/fhir/R4/{tenant_id}/. well-known/smart-configuration

📘

You cannot query directly using a web browser.

The abovementioned auth-code request will trigger a user login and, once the user is authenticated, will result in a consent screen that presents the user with the scopes (permissions) that your app requires. (You declared these scopes on the Developer Platform App Dashboard when you created the app). Once the user approves the scopes request, they are redirected back to the Redirect URL you provided on the App Dashboard when you created the app, and the app then retrieves a token (we will see that in the next step).

Here is some sample code that you can use to authorize a request [LINK]. It’s a Python/Flask app that generates a token which is available in the console and browser. You can then add your own code to the sample to use the token, or simply access the functionality as is, using the browser and copying the token for use with the command line tools like cURL.
To run the sample, you will need to generate a Self-signed Certificate and a Private Key (if you don’t already have SSL set up)

openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout pkey.pem -out cert.crt

Once you have the SSL set up you can configure and then run the sample Flask app with our security endpoints to test the OAuth flow.

Be sure to update the information in the Flask app.

Here is an example of what the auth-code link looks like. (This is the same link as found in the “Authenticate” button in the launch.html file that the Flask app serves):

<a href=”https://auth-api.login.greenwayhealth.com/oauth2/auth-code?client_id=XXXXXXXXXXXX&response_type=code&&scope=user%2FPatient.read%20openid%20fhirUser &aud=https://fhir-api.fhirprod.aws.greenwayhealth.com/fhir/R4/YYYYYYYYYY” >Authenticate</a>

Once you run the Flask app, click the button through to the authentication screen and then authenticate, you’ll see the auth code in the first few lines output on the terminal similar to the output below:

$ python3 ssl_server.py
 * Serving Flask app 'ssl_server'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on https://127.0.0.1:4567
 * Running on https://192.168.255.206:4567
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 823-965-749
192.168.255.206 - - [06/Sep/2022 17:28:41] "GET / HTTP/1.1" 200 -
192.168.255.206 - - [06/Sep/2022 17:28:41] "GET /AuthButton.png HTTP/1.1" 200 -
CODE: AXVYHSAhFvof2W-A5LOuyJ-bBJV3ulSfh8A59mFp
STATE: 42f8cdab

Trade the authorization code for a token

The app then exchanges the authorization code for an OAuth access token.
The full output on the terminal:

$ python3 ssl_server.py
 * Serving Flask app 'ssl_server'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on https://127.0.0.1:4567
 * Running on https://192.168.255.206:4567
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 823-965-749
192.168.255.206 - - [06/Sep/2022 17:28:41] "GET / HTTP/1.1" 200 -
192.168.255.206 - - [06/Sep/2022 17:28:41] "GET /AuthButton.png HTTP/1.1" 200 -
CODE: AXVYHSAhFvof2W-A5LOuyJ-bBJV3ulSfh8A59mFp
STATE: 42f8cdab
ACCESS TOKEN: eyJhbGciOiJSUzI1NiIsImtpZCI6IjNxMC1RMElKd1ltRnhzNlNZVXRPbEdrTnRhTSIsInBpLmF0bSI6InZ1dTMifQ.eyJzY29wZSI6InVzZXIvUGF0aWVudC5yZWFkIG9wZW5pZCBmaGlyVXNlciIsImNsaWVudF9pZCI6Ik16a3pOamswTmpFdE5UUmtNaTAwTVRNM0xUZ3dZMll0WW1WbVl6QTJabVUzWW1RdyIsImlzcyI6Imh0dHBzOi8vYXV0aC1hcGkuZ2lzZGV2LmF3cy5ncmVlbndheWhlYWx0aC5jb20vb2F1dGgyIiwic3ViIjoiM2VjMWZlMjktNGM5Mi00YjNjLTk3OGUtNjBkMjhlMmRhZWUwIiwicHJhY3RpY2UiOiIyMDEiLCJyb2xlIjoiVVNFUiIsInBlcnNvbmEiOiJwYXRpZW50IiwiZ2l2ZW5OYW1lIjoiVG9kMjY1Iiwib2lkIjoiIiwiZmhpclVzZXIiOiJQYXRpZW50L3VuZGVmaW5lZCIsInV1aWQiOiIzZWMxZmUyOS00YzkyLTRiM2MtOTc4ZS02MGQyOGUyZGFlZTAiLCJhdWQiOiJodHRwczovL2ZoaXItYXBpLmZoaXJkZXYuYXdzLmdyZWVud2F5aGVhbHRoLmNvbS9maGlyL1I0LzIuMTYuODQwLjEuMTEzODgzLjMuMTQwLjMwNDM0IiwiZmFtaWx5TmFtZSI6IktvemV5MzcwIiwiZW1haWwiOiJUb2QyNjUuS296ZXkzNzBAZG9tYWluLmNvbSIsInRlbmFudCI6IjIuMTYuODQwLjEuMTEzODgzLjMuMTQwLjMwNDM0IiwidXNlcm5hbWUiOiJUb2QyNjUiLCJleHAiOjE2NjI1MDMzMzd9.DsG8bVXXmhatX_48EU04e3wsbXkJG7YsT1b2TVcDYM73EudQApJW-AoVvvriIEpB5B8iM0zboy7yFvAmf2w-hcYi4pEoQD-DhjosYioHveNmn6wlDB2eFRsji0FLCYrx5-Mjk9r3HTcyXF5e3qNNVh4s2_WKXTMeQDfGA5R2lgCyxiFykgkWiRYkX0qCBxIe5VpHLAQY31jHHC0QDasaulplHBH6gOXnSuoSPRLEYZhKc4PnvGsp8aUyg4RXWl_bBQFME7JpaomI5LEN7v9E8y3vPGB6Ne-edq9OQwG8CHhjqzyotc2vBdRaxa20LPNxYXYnDD7nQQLPPb4mFVagmw

Behind the scenes above, the app has performed a POST to the token endpoint with the authorization code it received from the auth-code request

Use the token

Once you have the authorized token you can use it in http calls made in your own Python code to the FHIR API to retrieve data.

📘

A Note about Public Apps and Confidential Apps

Using OAuth and SMART-on-FHIR, we authenticate both the client (via the client ID) and the user (via their login). The example we illustrated earlier represents a generic authorization code workflow for what is considered a Public client application.

Public Applications
Public applications normally run on the user’s device and do not have a back-end server. Storing a shared secret on the user’s device to authenticate a Public client would not be appropriate, as a user could, in theory, retrieve the secret.

Confidential Applications
Confidential apps can safely store a client secret because the authentication exchange occurs on the back end, between the application server and the FHIR API, out of the reach of the user accessing the application through a browser.
Instead of solely relying on an authorization code workflow, a Confidential application can also possess a public/private key pair, where:

  1. The public key is declared on the Developer Platform.
  2. The app signs and submits a JWT token using its private key to the authentication server.

Mandatory PKCE Implementation for SMART-on-FHIR Applications

As part of the regulatory requirements under HTI-1, the use of Proof Key for Code Exchange (PKCE) is now mandatory for all SMART-on-FHIR apps. This update enhances the security of the authorization process and aligns with the regulatory standards to protect sensitive health information.

What is PKCE?
PKCE (Proof Key for Code Exchange) is an extension of the OAuth 2.0 authorization framework designed to enhance security by mitigating the risk of authorization code interception attacks. It strengthens the authorization process by requiring the use of a code verifier and a code challenge during the exchange of authorization codes for access tokens.

How PKCE Works:

  1. Generate a Code Verifier:
    • The client creates a random string, referred to as the code verifier.
  2. Create a Code Challenge:
    • The client generates a code challenge by applying the S256 transformation (SHA256 hash) to the code verifier.
    • Note: The Greenway Authorization Server supports only the S256 code challenge method. The plain code challenge method will be deprecated after October 20, 2025.
  3. Include Code Challenge in Authorization Request:
    • The client includes the code challenge and specifies the code challenge method (code_challenge_method=S256) in the authorization request.
  4. Exchange Authorization Code for Access Token:
    • During the token exchange, the client sends the code verifier to the authorization server.
    • The server validates the code challenge against the code verifier before issuing access tokens.
      For more detailed implementation guidelines, refer to the SMART 2.0 Implementation Guide.

For more detailed implementation guidelines, refer to the SMART 2.0 Implementation Guide.

Why is PKCE Required?
PKCE is required to:

  • Improve Security: Prevent malicious actors from intercepting authorization codes and gaining unauthorized access to sensitive health information.
  • Ensure Compliance: Meet the regulatory requirements set forth by HTI-1 for securing SMART-on-FHIR applications.

By mandating PKCE, we ensure that all applications adhere to modern security standards, protecting both users and developers from potential vulnerabilities.