Reputation: 31
I'm implementing a FHIR integration in Python to pull data from Epic's sandbox environment. I wanted to see if anyone else has attempted this, or has had any issues with token authentication when doing the kick-off request.
Some notable information on my build/progress:
I'm using https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token to get the token
The JWT passed back is valid and signed
Per Epic's bulk export specification, I'm using https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export as the endpoint to request data from.
To the above url, in my get request, I pass the following headers:
'Accept':'application/fhir+json',
'Content-type' : 'application/fhir+json', <- I've also tried removing this as it isn't explicitly stated to be passed
'Prefer':'respond-async',
'Authorization' : 'Bearer ' + token
I use a session to handle requests, and preserve the Authorization header by overriding NoRebuildAuthSession in requests.Session
The response from the get request comes back with a 401 error and the following information under www-authenticate: Bearer error="invalid_token", error_description="The access token provided is not valid"
Any guidance would be helpful! I think I might be using the wrong endpoint but I'm totally stumped right now.
class NoRebuildAuthSession(Session):
def rebuild_auth(self, prepared_request, response):
"""
No code here means requests will always preserve the
Authorization
header when redirected.
"""
session = NoRebuildAuthSession()
logging.basicConfig(level=logging.DEBUG)
instance = jwt.JWT()
message = {
# Client ID for non-production
'iss': '<client-id>',
'sub': '<client-id>',
'aud': 'https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token',
'jti': '<string of characters>',
'iat': get_int_from_datetime(datetime.now(timezone.utc)),
'exp': get_int_from_datetime(datetime.now(timezone.utc) + timedelta(minutes=1)),
}
# Load a RSA key from a PEM file.
with open('<private-key-path>', 'rb') as fh:
signing_key = jwt.jwk_from_pem(fh.read())
compact_jws = instance.encode(message, signing_key, alg='RS384')
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 = session.post('https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token', headers=headers, data=data)
responseDict = json.loads(x.text)
token = x.json()['access_token']
print(responseDict['access_token'])
headers = {
'Accept':'application/fhir+json',
'Content-type' : 'application/fhir+json',
'Prefer':'respond-async',
'Authorization' : 'Bearer ' + token
}
session.headers = headers
base = "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export"
y = session.get(base, headers = headers)
print(yaml.dump(y.headers, default_flow_style=False))
print(y.text)
Upvotes: 3
Views: 1020
Reputation: 967
As Cooper mentioned, your best resource is to contact Epic directly for troubleshooting assistance with their sandbox/APIs.
That said, glancing through your code, the URL does seem to be the issue. It seems like you're mimicking the Bulk FHIR tutorial from Epic, which may or may not be exactly reproducible in the sandbox.
Here, you're calling the Epic on FHIR endpoint to get the OAuth token:
x = session.post('https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token', headers=headers, data=data)
However, you then proceed to make the actual Bulk FHIR calls using the App Orchard endpoint:
base = "https://apporchard.epic.com/interconnect-aocurprd-oauth/api/FHIR/R4/Group/eIscQb2HmqkT.aPxBKDR1mIj3721CpVk1suC7rlu3yX83/$export"
This isn't correct. Assuming you are trying to use Epic on FHIR, the base URL to use is https://fhir.epic.com/interconnect-fhir-oauth
, so that line should read something like base = "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Group/..."
.
Upvotes: 3