Skip to content

SMART on FHIR Authorization

Overview

Kodjin FHIR Server APIs are secured by an OAuth 2.0 authorization server. The FHIR APIs endpoint security is based on the SMART authorization framework. This means that when making API calls to any FHIR API endpoint, the application (client) must pass a bearer token. This token is passed in the authorization header for each individual request. Registered application developers are therefore issued OAuth client credentials used to request access tokens from the Kodjin FHIR Server API authorization server.

References for this implementation:

The Kodjin FHIR Server supports the following methods for application authorization:

  • Two-legged OAuth in which apps use their OAuth client credentials to obtain an access token. This method is used for system-to-system interactions.
  • Three-legged OAuth in which apps must get the physical user authorization to obtain an access token in addition to providing their client credentials and being granted the scopes by the user to the app. This method is used for patient- or provider-facing apps accessing data in the Kodjin FHIR Server API.

If you intend to use the embedded patient-picker in Kodjin for setting up a patient context (for demo purposes only), please note that it does not support the multi-tenancy option.

Scopes

SMART on FHIR’s authorization methods use OAuth scopes to communicate (and negotiate) access requirements. An OAuth scope represents a permission. Scopes are used to let the client app know what resources an application needs to have access to. The level of API access afforded to an access token is determined by the OAuth scopes granted from the token request.

Scopes syntax

In the SMART app launch framework, there are three types of scopes: patient, user, and system. The syntax for these scopes is:

('patient' | 'user' | 'system') '/'([fhir-resource] | '*') '.' ('read' | 'write' | '*')
  • patient or user or system - scope level
  • fhir-resource - FHIR resource, e.g. Condition/
  • * - wildcard could be used to substitute any resource (all resources)
  • read or write or * - an access level. * - read and write access (full access)

Examples

Example - Allows an application to read a patient’s condition information for the particular patient.

patient/Condition.read

Example - Allows an application to read all observation resources.

system/Observation.read

Below are the descriptions of the permissions each type of scope grants.

Scope Grants
patient/*.read Permission to read any resource for the current patient (see notes on exposing wildcard scopes below).
user/*.* Permission to read and write all resources that the current user can access (see notes on exposing wildcard scopes below).
openid fhirUser Permission to retrieve information about the current logged-in user.
launch Permission to obtain launch context when app is launched from an EHR.
launch/patient When launching outside the EHR, ask for a patient to be selected at launch time.
offline_access Request a refresh_token that can be used to obtain a new access token to replace an expired one, even after the end-user no longer is online after the access token expires.
online_access Request a refresh_token that can be used to obtain a new access token to replace an expired one and that will be usable for as long as the end-user remains online.

Scopes evaluation

Client apps request scopes by calling the token endpoint or authorization endpoint. The app’s scope request must pass the following checks to determine whether each scope within the request is granted:

  • Is the scope included in the scope parameter of the request?
  • Is the scope approved for your app’s OAuth client credentials?
  • (For three-legged OAuth only) If the scope requires end-user consent, did the user grant that consent when prompted?

All scopes included in the request must pass the first two checks or the request will fail. If all requested scopes pass the first two checks and end-user consent is required for one or more of those scopes, then the user’s consent decision will determine the outcome of the request:

  1. If the user denies all requested scopes when prompted, then the request will fail.
  2. If the user grants one or more requested scopes, then the request will succeed. However, any scope the user denies will be omitted from the access token obtained through that request, preventing the app from accessing API endpoints requiring that scope. Token introspection can be used to check which scopes from the app’s request were granted.

Expanding wildcard scopes

How Kodjin FHIR Server APIs expands wildcard * scope:

  • patient/*.read expands to:

    • patient/Patient.read
    • patient/AllergyIntolerance.read
    • patient/CarePlan.read
    • patient/CareTeam.read
    • patient/Condition.read
    • patient/Device.read
    • patient/DiagnosticReport.read
    • patient/DocumentReference.read
    • patient/Goal.read
    • patient/Encounter.read
    • patient/Immunization.read
    • patient/MedicationRequest.read
    • patient/Observation.read
    • patient/Procedure.read
    • patient/Provenance.read
    • patient/Practitioner.read
    • patient/PractitionerRole.read
    • patient/ServiceRequest.read
    • patient/QuestionnaireResponse.read
    • patient/RelatedPerson.read
    • patient/Organization.read
    • patient/Location.read
  • user/*.read expands to:

    • user/Patient.read
    • user/AllergyIntolerance.read
    • user/CarePlan.read
    • user/CareTeam.read
    • user/Condition.read
    • user/Device.read
    • user/DiagnosticReport.read
    • user/DocumentReference.read
    • user/Goal.read
    • user/Group.read
    • user/Encounter.read
    • user/Immunization.read
    • user/MedicationRequest.read
    • user/Observation.read
    • user/Procedure.read
    • user/Provenance.read
    • user/Practitioner.read
    • user/PractitionerRole.read
    • user/ServiceRequest.read
    • user/QuestionnaireResponse.read
    • user/RelatedPerson.read
    • user/Organization.read
    • user/Location.read
  • system/*.read expands to:

    • system/AllergyIntolerance.read
    • system/Binary.read
    • system/CarePlan.read
    • system/CareTeam.read
    • system/Condition.read
    • system/Device.read
    • system/DiagnosticReport.read
    • system/DocumentReference.read
    • system/Encounter.read
    • system/Goal.read
    • system/Group.read
    • system/Immunization.read
    • system/Location.read
    • system/Medication.read
    • system/MedicationRequest.read
    • system/Observation.read
    • system/Organization.read
    • system/Patient.read
    • system/Practitioner.read
    • system/PractitionerRole.read
    • system/Procedure.read
    • system/Provenance.read
    • system/ServiceRequest.read
    • system/QuestionnaireResponse.read
    • system/RelatedPerson.read

Specific scopes

Kodjin provides specific scopes to secure endpoints that stands out form standard FHIR API.

The scopes for Bulk Export/Import protection are:

  • user|system|patient/Export.write - Allows starting the export operation (kick-off request & delete).
  • user|system|patient/Export.read - Allows checking the results (status request).
  • user|system|patient/Import.write - Allows reading import data.
  • user|system|patient/Import.read - Allows checking the results.

The scopes for operations are:

  • user|system|patient/kodjin/DataMapperTemplate.read - Allows read templates for Data Mapper
  • user|system|patient/kodjin/DataMapperTemplate.write - Allows write templates for Data Mapper

The scopes for Bulk Export/Import protection are:

The scopes for FHIR base URL

  • user|system/kodjin/fhir-base-url - Allows all interactions with endpoint [base]/fhir-base-url

Scopes for operations could be replaced with * (wildcard), for example system/*/* - allows all opearions for system-to-system interactions

Expanding * (wildcard) for operations allows only operation and does not allow read or write of FHIR resources. To expand with * (wildcard) permission for FHIR resources other pattern should be used: system/*.*. Read more in Scopes syntax about scopes for FHIR resources

SMART on FHIR Authorization flow

Overview

The Smart application (client) can launch from within an EHR (if supported) or patient portal session; this is known as an EHR launch. Alternatively, it can launch as a stand-alone application.

Once an app receives a launch request, it requests authorization to access an FHIR resource by causing the browser to navigate to the Kodjin FHIR Server API authorization endpoint.

On receiving the launch notification, the app would query the issuer’s /metadata endpoint or .well-known/smart-configuration endpoint, which contains the OAuth authorize endpoints and token endpoint URLs for use in requesting authorization to access FHIR resources.

Based on registered client rules and possibly end-user authorization, the Kodjin FHIR Server API authorization server either grants the request (as described above in the scopes evaluation) by returning an authorization code to the app’s redirect URL or denies the request. The app then exchanges the authorization code for an access token, which the app presents to the Kodjin FHIR Server API to access requested FHIR resources. If a refresh token is returned along with the access token, the app may use this to request a new access token with the same scope once the access token expires.

Implementation notes

  • Before a SMART app can run against an Kodjin FHIR Server API, the app must be registered with the authorization service.
  • Proof Key for Code Exchange (PKCE) doesn't supported.
  • The Refresh token is valid not less than 90 days.

Authorization request

Step 1. The developer's application (client) asks for authorization

At launch time, the app generates a request for authorization by supplying the following parameters to the Kodjin FHIR Server API authorization endpoint.

The app SHALL use an unpredictable value for the state parameter with at least 122 bits of entropy (e.g., a properly configured random uuid is suitable). The app SHALL validate the value of the state parameter upon return to the redirect URL and SHALL ensure that the state value is securely tied to the user’s current session (e.g., by relating the state value to a session identifier issued by the app). The app SHOULD limit the grants, scope, and period of time requested to the minimum necessary.

If the app needs to authenticate the identity of the end-user, it should include two OpenID Connect scopes: openid and fhirUser. When these scopes are requested and the request is granted, the app will receive an id_token along with the access token.

Parameters:

  • response_type - Fixed value: code (required).
  • client_id - The client's identifier (required).
  • redirect_uri - Must match one of the client's pre-registered redirect URIs (required).
  • launch - When using the EHR launch flow, this must match the launch value received from the Kodjin FHIR Server API (optional).
  • scope - Must describe the access that the app needs, including clinical data scopes like patient/*.read, openid, and fhirUser (optional).
  • state - An opaque value used by the client to maintain state between the request and callback. The authorization server includes this value when redirecting the user-agent back to the client. The parameter SHALL be used for preventing cross-site request forgery or session fixation attacks (required).
  • aud - URL of the Kodjin FHIR Server API server from which the app wishes to retrieve FHIR data Service Base urls. This parameter prevents leaking a genuine bearer token to a counterfeit resource server (Note: in the case of an EHR launch flow, this aud value is the same as the launch's iss value.) (required).

Step 2. Kodjin FHIR Server API authorization server evaluates authorization request, asking for end-user input

The authorization decision is up to the authorization server, which may request authorization from the end-user. The authorization server will enforce access rules based on local policies and optionally direct end-user input.

The Kodjin FHIR Server API decides whether to grant or deny access. This decision is communicated to the app (client) when the authorization server returns an authorization code (or, if denying access, an error response). Authorization codes are short-lived, usually expiring within around one minute. The code is sent when the authorization server causes the browser to navigate to the app’s redirect_uri, with the following URL parameters:

Parameters:

  • code - The authorization code generated by the authorization server. The authorization code must expire shortly after it is issued to mitigate the risk of leaks (required).
  • state - The exact value received from the client (required).

Step 3. App exchanges authorization code for access token

After obtaining an authorization code, the app trades the code for an access token via HTTP POST to the authorization server’s token endpoint URL, using content-type application/x-www-form-urlencoded, as described in section 4.1.3 of RFC6749.

  • If a client has registered for Client Password authentication (i.e., it possesses a client_secret that is also known to the EHR), the client authenticates using an Authorization header with HTTP Basic authentication, where the username is the app’s client_id and the password is the app’s client_secret.

  • If a client has registered for JWT assertion-based authentication (i.e., it possesses a public/private key-pair whose public key is known to the EHR), the client authenticates using two parameters: client_assertion_type and client_assertion, as profiled in SMART Backend Services Protocol Details.

Parameters:

  • grant_type - Fixed value: authorization_code (required).
  • code - Code that the app received from the authorization server (required).
  • redirect_uri - The same redirect_uri used in the initial authorization request (required).
  • client_id - Required for public apps. Omit for confidential apps (conditional).

The authorization server SHALL return a JSON object that includes an access token or a message indicating that the authorization request has been denied. The JSON structure includes the following parameters:

Parameters:

  • access_token - The access token issued by the authorization server (required).
  • token_type - Fixed value: Bearer (required).
  • expires_in - Lifetime in seconds of the access token, after which the token SHALL NOT be accepted by the FHIR resource server (recommended).
  • scope - Scope of access authorized. Note that this can be different from the scopes requested by the app (required).
  • id_token - Authenticated patient identity and user details, if requested (optional).
  • refresh_token - Token that can be used to obtain a new access token, using the same or a subset of the original authorization grants (optional).

In addition, if the app was launched from within a patient context, parameters to communicate the context values MAY BE included. For example, a parameter like "patient": "123" would indicate the FHIR resource https://[fhir-base]/Patient/123. Other context parameters may also be available.

The parameters are included in the entity-body of the HTTP response, as described in section 5.1 of RFC6749.

The access token is a string of characters as defined in RFC6749 and RFC6750. The token is essentially a private message that the authorization server passes to the FHIR Resource Server, telling the FHIR server that the “message bearer” has been authorized to access the specified resources. Defining the format and content of the access token is left up to the organization that issues the access token and holds the requested resource.

The authorization server’s response SHALL include the HTTP “Cache-Control” response header field with a value of “no-store,” as well as the “Pragma” response header field with a value of “no-cache.”

The authorization server decides what expires_in value to assign to an access token and whether to issue a refresh token, as defined in section 1.5 of RFC6749, along with the access token. If the app receives a refresh token along with the access token, it can exchange this refresh token for a new access token when the current access token expires (see step 5 below). A refresh token SHALL BE bound to the same client_id and SHALL contain the same, or a subset of, the set of claims authorized for the access token with which it is associated.

Apps SHOULD store tokens in app-specific storage locations only, not in system-wide-discoverable locations. Access tokens SHOULD have a valid lifetime no greater than one hour. Confidential clients may be issued longer-lived tokens than public clients.

At this point, the authorization flow is complete. Follow the steps below to work with data and refresh access tokens.

Step 4. The app accesses clinical data via Kodjin FHIR Server API

With a valid access token, the app (client) can access protected FHIR data by issuing a FHIR API call to the FHIR endpoint on the Kodjin FHIR Server API FHIR resource server. The request includes an Authorization header that presents the access_token as a “Bearer” token:

Authorization: Bearer {{access_token}}

Example -Request

GET https://demo.kodjin.com/fhir/Patient/123
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuZWVkX3BhdGllbnRfYmFubmVyIjp0cnVlLCJzbWFydF9zdHlsZV91cmwiOiJodHRwczovL2xhdW5jaC5zbWFydGhlYWx0aGl0Lm9yZy9zbWFydC1zdHlsZS5qc29uIiwicGF0aWVudCI6ImYzZWNmNjkwLWUwMzUtNDk4ZC05ZThjLTFlZjFlNGRiMzRiNyIsImVuY291bnRlciI6IjQ2MzJlNjFiLTliMzQtNGFkNy1hNDMxLWYwMDhmMzViNGRkMyIsInJlZnJlc2hfdG9rZW4iOiJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpJVXpJMU5pSjkuZXlKamIyNTBaWGgwSWpwN0ltNWxaV1JmY0dGMGFXVnVkRjlpWVc1dVpYSWlPblJ5ZFdVc0luTnRZWEowWDNOMGVXeGxYM1Z5YkNJNkltaDBkSEJ6T2k4dmJHRjFibU5vTG5OdFlYSjBhR1ZoYkhSb2FYUXViM0puTDNOdFlYSjBMWE4wZVd4bExtcHpiMjRpTENKd1lYUnBaVzUwSWpvaVpqTmxZMlkyT1RBdFpUQXpOUzAwT1Roa0xUbGxPR010TVdWbU1XVTBaR0l6TkdJM0lpd2laVzVqYjNWdWRHVnlJam9pTkRZek1tVTJNV0l0T1dJek5DMDBZV1EzTFdFME16RXRaakF3T0dZek5XSTBaR1F6SW4wc0ltTnNhV1Z1ZEY5cFpDSTZJbTE1WDNkbFlsOWhjSEFpTENKelkyOXdaU0k2SW05d1pXNXBaQ0J3Y205bWFXeGxJRzltWm14cGJtVmZZV05qWlhOeklIVnpaWEl2S2k0cUlIQmhkR2xsYm5RdktpNHFJR3hoZFc1amFDOWxibU52ZFc1MFpYSWdiR0YxYm1Ob0wzQmhkR2xsYm5RaUxDSjFjMlZ5SWpvaVVISmhZM1JwZEdsdmJtVnlMMU5OUVZKVUxURXlNelFpTENKcFlYUWlPakUxTWprMk56TTVNRElzSW1WNGNDSTZNVFV5T1RZM05ESXdNbjAudDZZbjlOd0RZWU5IeWlOdXQ2N2xpOFRENzZZX0MtanEwVlExTVBFTGpXSSIsInRva2VuX3R5cGUiOiJiZWFyZXIiLCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIG9mZmxpbmVfYWNjZXNzIHVzZXIvKi4qIHBhdGllbnQvKi4qIGxhdW5jaC9lbmNvdW50ZXIgbGF1bmNoL3BhdGllbnQiLCJjbGllbnRfaWQiOiJteV93ZWJfYXBwIiwiZXhwaXJlc19pbiI6MzYwMCwiaWRfdG9rZW4iOiJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpTVXpJMU5pSjkuZXlKd2NtOW1hV3hsSWpvaVVISmhZM1JwZEdsdmJtVnlMMU5OUVZKVUxURXlNelFpTENKaGRXUWlPaUp0ZVY5M1pXSmZZWEJ3SWl3aWMzVmlJam9pTnpaa05UTm1aalpqWTJRMk9XVmhNamRtTXpJek9UTTRNR0l6TURNNVlqUmhPRGN5T1RreVpqZ3hOVEZsWWpNeE9HTmxNVGcyWldRNVpqSm1NekV6WXlJc0ltbHpjeUk2SW1oMGRIQTZMeTlzWVhWdVkyZ3VjMjFoY25Sb1pXRnNkR2hwZEM1dmNtY2lMQ0pwWVhRaU9qRTFNamsyTnpNNU1EY3NJbVY0Y0NJNk1UVXlPVFkzTnpVd04zMC5LanJScTJYZ2lPYk0wQXpOcTVIcmUzd2tRRlFGNHhjc0Y3YzlnVnpWOHF6N0x2eXc0bnd1blBxbTlUdmJFaEYzM2k5anZPb1RUS0pjVTJJMGt1eHR3bEYzLUJ6WjdoZjRBWUZVY1poME9BZkFVRjNIaFRmRUQ5R1MxVmlDWjJBUmNkRWdyc0dqTk83OGFlODd2M2hlNFQtaG9KWkZ2T2FXaWZPaEdqcFA1WW14eEdpaGozbDluckpVZmtId0V6VFdCaUo4TEpQOVVCRXFZaTBHclc0TzJCRnJJSk14YWNyQmVrbkJ3cHJJbnJKNlg5TkpJbGpkVEI1Yk1iT1BPNVJGbDBldFdXMDVTNU1NRmdZT3duemJHM0xOa3YyWnlINWRSeDRoQWhaRVhFLW1aZndFSUVVRkc4WHdZeHc5WVNzSTlPOFVhQUh6OGtNQkVVSEg4U01ad2ciLCJpYXQiOjE1Mjk2NzM5MDcsImV4cCI6MTUyOTY3NzUwN30.RdYLx5aMfXdLdRTuzkvKB0jo_ZRqXSxcamrl7mwcEi0

The Kodjin FHIR Server API SHALL validate the access token and ensure that it has not expired and that its scope covers the requested resource.

Step 5. The app uses a refresh token to obtain a new access token.

Refresh tokens are issued to enable sessions to last longer than the validity period of an access token. The app can use the expires_in field from the token response to determine when its access token will expire.

The Token Introspection Protocol (OAuth 2.0) is used to provide an introspection endpoint that clients can use to examine the validity and meaning of tokens. An app with “online access” can continue to get new access tokens as long as the end-user remains online. Apps with “offline access” can continue to get new access tokens without the user being interactively engaged for cases where an application should have long-term access extending beyond the time when a user is still interacting with the client.

The app requests a refresh token in its authorization request via the online_access or offline_access scope. If granted, the authorization server supplies a refresh_token in the token response. After an access token expires, the app requests a new access token by providing its refresh token to the token endpoint. An HTTP POST transaction is made to the authorization server’s token URL, with content-type application/x-www-form-urlencoded. The decision about how long the refresh token lasts is determined by a mechanism that the server chooses. For clients with online access, the goal is to ensure that the user is still online.

Parameters:

  • grant_type - Fixed value: refresh_token (required).
  • refresh_token - The refresh token from a prior authorization response (required).
  • scope - The scopes of access requested. If present, this value must be a strict subset of the scopes granted in the original launch (no new permissions can be obtained at refresh time). A missing value indicates a request for the same scopes granted in the original launch (optional).

The response is a JSON object containing a new access token with the following claims:

JSON object property name:

  • access_token - New access token issued by the authorization server (required).
  • token_type - Fixed value: bearer (required).
  • expires_in - The lifetime in seconds of the access token. For example, the value 3600 denotes that the access token will expire in one hour from the time the response was generated (required).
  • scope - Scope of access authorized. Note that this will be the same as the scope of the original access token, and it can be different from the scopes requested by the app (required).
  • refresh_token - The refresh token issued by the authorization server. If present, the app should discard any previous refresh_token associated with this launch, replacing it with this new value (optional).

Note

An app may be able to replace an expired access token automatically, without user interaction.