pythonpython-requestsjwthl7-fhirsmart-on-fhir

Fhir Epic Sandbox : Using a JWT to Obtain an Access Token for a Backend Service


I'm trying to use the sandbox from https://fhir.epic.com/ for Backend Services.

I am following this tutorial : https://fhir.epic.com/Documentation?docId=oauth2&section=BackendOAuth2Guide :

But I cannot POST the JWT to the endpoint to obtain the access token. I should send a POST request to this URL: https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token.

I'm using python and this is my code so far:

import json
import requests
from datetime import datetime, timedelta, timezone
from requests.structures import CaseInsensitiveDict
from jwt import (
    JWT,
    jwk_from_dict,
    jwk_from_pem,
)
from jwt.utils import get_int_from_datetime


def main():
    instance = JWT()
    message = {
        # Client ID for non-production
        'iss': '990573e-13e3-143b-8b03-4fbb577b660',
        'sub': '990573e-13e3-143b-8b03-4fbb577b660',
        'aud': 'https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token',
        'jti': 'f9eaafba-2e49-11ea-8880-5ce0c5aee679',
        'iat': get_int_from_datetime(datetime.now(timezone.utc)),
        'exp': get_int_from_datetime(datetime.now(timezone.utc) + timedelta(hours=1)),
    }

    # Load a RSA key from a PEM file.
    with open('/home/user/ssl/privatekey.pem', 'rb') as fh:
        signing_key = jwk_from_pem(fh.read())

    compact_jws = instance.encode(message, signing_key, alg='RS384')
    print(compact_jws)

    headers = CaseInsensitiveDict()
    headers['Content-Type'] = 'application/x-www-form-urlencoded'

    data = {
      'grant_type': 'client_credentials',
      'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
      'client_assertion': compact_jws
    }
    
    x = requests.post('https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token', headers=headers, data=data)
    print(x.text)

But I always get a 400 error:

{
  "error": "invalid_client",
  "error_description": null
}

Is the URL correct? How can I get an Access Token to play with the Sandbox?


Solution

  • 'exp': get_int_from_datetime(datetime.now(timezone.utc) + timedelta(hours=1)),
    

    At first glance, this appears to be your issue. Epic requires that exp be no more than 5 minutes in the future.

    Couple of pieces of advice, beyond that:

    1. Use a library available from jwt.io
    2. Jwt.io also has a debugger where you can paste in your JWT to verify it is valid