Patient Portal Authentication (via FHIR)

This guide shows you how to integrate OneRecord API using a method that requires patients to log in with their username and password to third-party health portals. By following this method, your solution will be able to securely access and query patient data from the connected FHIR health networks, ensuring a seamless and secure data retrieval process.

The following summarizes a patient-authenticated data query using OneRecord API:

  1. Use OneRecord's Directory Service to look up the partner portal URLs to which your OneRecord API account has been granted access. You can retrieve a list of all accessible endpoints or filter based upon the third-party health organization's name or zip code. More Details
  2. Collect the redirect URLs for the desired partner portals you wish to query (from the response in Step 1). At this step, each URL you wish to use must be appended with a "clientURL" query parameter, which is the URL encoded location within your own app where the patient will be redirected after successfully authenticating with the third-party portal. Ensure that the "clientURL" page can accept "code" and "state" URL parameters, as they are necessary for obtaining an access token to retrieve patient records. More Details
  3. Redirect the user (patient) to the URLs you modified (in Step 2) so they can authenticate and authorize data fetches made by your solution. This step authorizes two things. It makes sure your solution is authorized to access that patient portal (via clientId), and also authorizes the patient with their credentials. More Details
  4. Upon successful authentication, the user is redirected back to your solution, to your supplied redirect_uri from Step 2. This page in your solution should accept URL parameters named "code" and "state", which are sent by the patient portal. These can be sent to OneRecord's Integrator Service to generate a patient access token for the specific portal that they authorized. More Details
  5. Once you've obtained an access token (in Step 4), you can use it to perform records fetches through OneRecord's Integrator Service for as long as it lives. More Details

This process demonstrates how access to personal health information (PHI) is securely protected with OneRecord API as the gate keeper. The generation of patient access tokens requires authorization of your own platform as well as the patient's credentials.

Accessing OneRecord API

To interact with OneRecord API, you must provide your Device's API Access Token in the Authorization header as a Bearer token in all requests to OneRecord Directory or Integrator Services. To learn more about obtaining OneRecord Device API Access Tokens, see the Getting Started Integration Guide.

Request Header:

Authorization: Bearer <device-api--access-token>

The URLs to Directory and Integrator Services on this page are intended to be used with your sandbox API Access Token. For production access, see the documentation about Going Live

Looking Up Partner Portals (Through Directory Service)

Use OneRecord's Directory Service to search for partner health portals that your solution was securely linked to during the OneRecord onboarding process.

📘

Note

You can retrieve a list of all accessible endpoints or filter based upon the third-party health organization's name or zip code.

Partner Portals are divided into two categories: clinical and payer. As an integrator, you must know in advance which type you are seeking to query. In the Directory search, you can fetch all endpoints for a given category by omitting the search filter term.

The following URLs can be used to search for partner portals for clinical and payers, respectively:

GET https://stage.directory.onerecord.com/directory/v1/client/endpoint/clinical/search/{name-or-zipcode-search-string}
GET https://stage.directory.onerecord.com/directory/v1/client/endpoint/payer/search/{name-or-zipcode-search-string}

An example response from these endpoints is shown below:

[
    {
        "name": "Epic New",
        "city": "Verona",
        "state": "WI",
        "street": "1979 Milky Way",
        "zip": "53593",
        "vendor": "Epic",
        "endpointStatus": "active",
        "disposition": "1",
        "dataType": "clinical",
        "derivedAuthUri": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/authorize?client_id=<client-id>&redirect_uri=https%3A%2F%2Fredirector.onerecord.com%2Ffhir%2Finbound&response_type=code",
        "redirectUri": "https://redirector.onerecord.com/outbound/?portalURL=https%3A%2F%2Ffhir.epic.com%2Finterconnect-fhir-oauth%2Foauth2%2Fauthorize%3Fclient_id%3De9896805-87ce-4da3-991c-093e4d1a53c5%26redirect_uri%3Dhttps%253A%252F%252Fdevelop.onerecord.com%252Ffhir%252Fauth%26response_type%3Dcode",
        "latitude": 42.995075,
        "longitude": -89.567826,
        "location": {
            "lat": 42.995075,
            "lon": -89.567826
        },
        "_id": "8e9cb0f7-f920-49c3-ad23-b54b8a9ebe9f",
        "parent_org": "Sandbox"
    }
]

Preparing Authorization URLs

Before using the "redirectUri" provided by the OneRecord Directory Service to authorize the user, some preparation is required. To prepare this URL for use, you will need to ensure it contains the "portalURL" and "clientURL" query parameters.

The "portalURL" is already provided as a URL encoded string, while you will need to supply the "clientURL" parameter, which should be a URL encoded string as well. The "clientURL" is the location within your solution where the user will be redirected after successfully authenticating with the health portal.

For instance, suppose your application's desired return URL is https://client-app.com/authcode, and you receive the following "redirectUri" from the Directory Service:

https://redirector.onerecord.com/outbound/?portalURL=https%3A%2F%2Ffhir.epic.com%2Finterconnect-fhir-oauth%2Foauth2%2Fauthorize%3Fclient_id%3De9896805-87ce-4da3-991c-093e4d1a53c5%26redirect_uri%3Dhttps%253A%252F%252Fdevelop.onerecord.com%252Ffhir%252Fauth%26response_type%3Dcode

You must modify this URL as follows before sending the user to it for authorization:

https://redirector.onerecord.com/outbound/?portalURL=https%3A%2F%2Ffhir.epic.com%2Finterconnect-fhir-oauth%2Foauth2%2Fauthorize%3Fclient_id%3De9896805-87ce-4da3-991c-093e4d1a53c5%26redirect_uri%3Dhttps%253A%252F%252Fdevelop.onerecord.com%252Ffhir%252Fauth%26response_type%3Dcode&clientURL=https%3A%2F%2Fclient-app.com%2Fauthcode

The OneRecord Redirector Service is responsible for managing both outbound and inbound traffic to and from your solution. Outbound traffic is directed to the "portalURL" query parameter, and inbound traffic is directed to the "clientURL" query parameter.

📘

Important Note

The page hosted at "clientURL" must be able to accept "code" and "state" URL parameters, as these are the signed credentials sent back by the partner portal. This data is crucial for obtaining an access token that authorizes your solution to retrieve patient records.

Handling PKCE Security

Some partner portals have an additional layer of security built in called PKCE (Proof Key for Code Exchange). More information about PKCE can be found here: https://oauth.net/2/pkce/

If the portal you are trying to reach is enabled with PKCE security, an additional query parameter called code_challenge_method will appear in its derivedAuthUri returned from the Directory Search. If this parameter exists for the portal you wish to use, you must generate a challenge with the given hash type (from code_challenge_method) and set it as the code_challenge query parameter in the "portalURL" of your "redirectUri" before redirecting the user there.

To generate the code_challenge, follow these steps:

  1. Generate a random code_verifier string.
  2. Compute the hash of the code_verifier using the hash method specified in the code_challenge_method parameter.
  3. Convert the hash to a URL-safe string.

The code_verifier that you chose before you hashed it into a code_challenge must also be sent in the POST body when generating the patient's access token later on, as the "authorizationCodeVerifier" field. This added security measure ensures the integrity of the authorization process and protects against potential attacks.

For example, in a derivedAuthUrl looking like this:

https://memberlogin.bcbsfl.com/auth/oauth2/authorize?code_challenge={codechallenge}&code_challenge_method=S256&response_type=code&client_id=xxxxx&redirect_uri=https%3A%2F%2Fredirector.onerecord.com%2Finbound%2F&scope=patient%2F%2A.read

Your solution should convert it to something like this:

https://memberlogin.bcbsfl.com/auth/oauth2/authorize?code_challenge=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU&code_challenge_method=S256&response_type=code&client_id=xxxxx&redirect_uri=https%3A%2F%2Fredirector.onerecord.com%2Finbound%2F&scope=patient%2F%2A.read

Once you have this final result, replace the "portalUrl" query parameter of your "redirectUri" with the URL encoded string version of your new URL with added code_challenege.

Sending Patient To Health Portal For Login

If you have properly configured the portal URL with your return location, you can then send the user there. At this point, they will see a login screen branded to the health organization that maintains the portal.

Once the patient passes the access challenges of the portal, a code (and sometimes state param) is generated and passed when redirected back to your solution. The "code" and "state" parameters are sent in the URL, and it is ultimately up to your solution to use these parameters to obtain the patient access token. The instructions for setting "clientURL" remain the same as in the previous section.

📘

Note

The state query parameter is only required by some portals. Sometimes it will be omitted. It is important for the successful functioning of authentication in your solution to use this parameter if it is populated.

It is essential to remember the endpoint ID through which the patient is logging into so that it can be provided again when it's time to generate the patient's access token. This ID will be necessary for querying the FHIR resources on the specific portal that the patient authorized in subsequent steps.

Obtaining A Patient Access Token (Through Integrator Service)

Send the data collected from previous steps through following Integrator Service URL, or Try It Here and it will generate a patient access token:

POST https://stage.integrator.onerecord.com/integrator/v1/fhir/auth/{endpoint-id}/token

The endpoint-id in the URL should be replaced with the _id field for the desired portal from the Directory Search response earlier. Send a request body like the following to obtain the access token for that portal:

{
    "state": "1234",
    "authorizationCode": "r234r26re6256te26t",
    "authorizationCodeVerifier": "vctNm4z99ZL4XSs5m1uSHuRa0CRsXI.p0C8apak_B4loVuc~w2m4Us6_OWCb-fzVHu8WEkq3eygy5-RSCY0D2wqLeC3dgaI8Px20NwfVD.RS~xyArO1OKUwIF7c-_8.K"
}

authorizationCode should be populated with the "code" query parameter that was included when the user was sent from the patient portal back to your solution's redirect url. If included, use the value in the "state" query parameter for the corresponding "state" input parameter. "state" input can be omitted if it was not provided in the redirect from the customer portal.

authorizationCodeVerifier is only required when the partner portal requires additional PKCE security. If so, provide the code_verifier string that you used when generating your PKCE code_challenge up above.

The following is an example response for the access token request:

{
    "patId": "12312-21213",
    "token": "957b880d-8e6f-4fba-82cf-b1c8359cb832",
    "tokenExpires": "28799",
    "refreshToken": "74cf555d-545c-4bfb-8563-a0f0a9261999",
    "endpointId": "a05bfae7-d8a0-42fa-8159-c0305066ab37"
}

You will need to retain token and patId from this response in order to query FHIR resources. Some FHIR servers embed the patient id into their token, so it will be omitted in these cases, but your solution should be prepared to handle patId when it exists.

Refreshing A Patient Access Token

In some cases, a refresh token is provided in the response when generating the patient access token. Refresh tokens have an extended lifetime compared to access tokens, and they can be used to renew an access token without requiring the user to go through the OAuth process again using their credentials. The lifetime of the refresh token is set by the FHIR server but is, unfortunately, not usually shared with the consumer in the response.

To use a refresh token, instead of sending code and state to this API service, you should send the following body:

{
    "refreshToken": "74cf555d-545c-4bfb-8563-a0f0a9261999"
}

Keep in mind that not all partner data portals provide refresh tokens. When a new access token is obtained using a refresh token, a new refresh token may also be provided by the partner data portal, which will be used for future refreshes. It's important to securely store refresh tokens, as they allow access to the patient's data even after the original access token has expired.

Unless provided by the partner portal, there is no way for OneRecord to be able to accurately tell you the expires length of access and / or refresh tokens.

Retrieving FHIR Resources (Through Integrator Service)

With a valid patientId and access token, you can now fetch FHIR resources for the specified patient within the authorized portal for as long as the access token remains valid or can be refreshed. To query FHIR resources, Try It Here, or use the Integrator Service URL:

POST https://stage.integrator.onerecord.com/integrator/v1/fhir/resources/{endpoint-id}

This request is a POST with the following payload:

{
  "patientId": "12312-21213",
  "oauthToken": "957b880d-8e6f-4fba-82cf-b1c8359cb832"
}

Asynchronous FHIR Queries

At times, some FHIR queries may take an extended period to process during retrieval. Understanding this, OneRecord API provides integrators with an option to send FHIR Query transactions that process in the background. This eliminates the need for your solution to wait for a response, and instead, provides the flexibility to direct your users to other useful pages or activities while waiting for the FHIR query to complete. This feature equips integrators of OneRecord API with the capability to control a more fine-grained user experience in their solutions.

An asynchronous FHIR Query request is a two-step process.

  1. Initialize the FHIR Query: In the first step, the request for a FHIR query is initialized, and a transactionId is recorded. This transactionId serves as a unique identifier for your specific FHIR Query request.
POST https://stage.integrator.onerecord.com/integrator/v1/fhir/async/resources/{endpoint-id}
{
  "oauthToken": "string",
  "patientId": "string",
  "resources": [
    "string"
  ],
  "params": {
    "fromTime": "2023-06-08T21:10:50.390Z",
    "toTime": "2023-06-08T21:10:50.391Z",
    "excludeUndatedEntries": true
  }
}
{
  "errorData": {
    "errors": [
      {
        "timestamp": "string",
        "code": 0,
        "severity": "string",
        "status": 0,
        "error": "string",
        "message": "string",
        "path": "string"
      }
    ],
    "highestError": "string"
  },
  "transactionId": "string",
  "deviceId": "string"
}

The response headers will contain an entry named OneRecord-Transaction-ID, which is used to retrieve the result in the next step.

  1. Request the Associated Data: With your recorded transactionId, you can make a request to OneRecord API to retrieve the associated healthcare data or error response. We recommend making this request in intervals of your choosing (for example, every 30 seconds) to check when the data becomes available.
GET https://stage.integrator.onerecord.com/integrator/v1/fhir/cache/retrieve/{transactionId}
{
  "errorData": {
    "errors": [
      {
        "timestamp": "string",
        "code": 0,
        "severity": "string",
        "status": 0,
        "error": "string",
        "message": "string",
        "path": "string"
      }
    ],
    "highestError": "string"
  },
  "fhirVersion": "string",
  "contentType": "string",
  "payload": {}
}

This request can be made as many times as necessary. If the healthcare data or error response is not yet available, you will be notified accordingly. Once the result becomes available in the cache, you will receive a response whether the transaction resulted in an error or success.

Results are cached and available for two hours after they first become available. Results remain in the cache until their expiration, allowing for multiple pulls if necessary. This ensures healthcare data accessibility and enhances the flexibility of your solution when handling extensive FHIR queries.

Filtering FHIR Resources

The default FHIR request returns all available FHIR resources from the endpoint. If you only need a specific subset of FHIR resources, you can filter the list by including the desired resources in your request like this:

{
  "patientId": "12312-21213",
  "oauthToken": "957b880d-8e6f-4fba-82cf-b1c8359cb832",
  "resources": [
    "Patient",
    "ExplanationOfBenefit",
    "Coverage"
  ]
}

The full list of supported FHIR resources that you can request from a FHIR query includes:

{
  "resources": [ 
        "Patient", 
        "Immunization", 
        "AllergyIntolerance", 
        "MedicationRequest", 
        "MedicationStatement", 
        "Vitals", 
        "Labs", 
        "DiagnosticReport", 
        "Condition", 
        "Procedure", 
        "FamilyMemberHistory", 
        "DocumentReference", 
        "CareTeam",
        "CarePlan",
        "ExplanationOfBenefit", 
        "Coverage"
    ]
}

By providing a filtered list of resources in your FHIR request, you can ensure that you only receive the specific data you need for your integrated solution. Be sure to include an example POST body to demonstrate how developers can use filtering when making requests to the Integrator Service URL.

FHIR Data Responses

The response from FHIR queries will return FHIR data in R4 format. This format is one of the most easily consumable interoperability formats used in the healthcare industry. For more information about common interoperability formats and how to work with them, refer to the Data Formats section of OneRecord's API documentation. OneRecord strives to provide the most user-friendly data formats to API users, even if most of the partner portals store data in a more common, older format like DSTU2.

By using the FHIR R4 format, OneRecord ensures that developers can easily work with returned data, regardless of the formats used by individual partner portals. This approach simplifies the integration process and allows developers to focus on building their applications without worrying about compatibility issues.

Wrapping Up

In conclusion, this guide has provided a comprehensive overview of the Patient Portal Authentication method for querying medical records using OneRecord API. The key steps covered are:

  1. Using OneRecord Directory Service to look up partner portal URLs and retrieve a list of accessible endpoints.
  2. Preparing authorization URLs with your desired return location and handling additional security layers such as PKCE if required.
  3. Redirecting the patient to the redirectUri you prepared, and receiving back code and state parameters from the health portal.
  4. Obtaining a patient access token using the code and state parameters and handling refresh tokens for extended access.
  5. Retrieving and filtering FHIR resources from partner portals using OneRecord Integrator Service.

Throughout the process, the importance of securely managing tokens, protecting patient data, and ensuring seamless integration with partner health networks has been emphasized. By following the steps outlined in this guide, developers can build a robust and secure solution for querying medical records with FHIR and providing valuable services to patients and healthcare providers.