SMART: Scopes
The OAuth2 specification uses "scopes" as a mechanism for an application to request specific API permissions for actions it wishes to perform.
When setting up a new SMART on FHIR application that will consume FHIR APIs, one of the most important security considerations is which scopes to allow the app to request. The purpose of scopes is sometimes misunderstood: You can think of a scope as being a mechanism to allow an app to request the permission to do something on the user's behalf. So an app that only provides data viewing capabilities of some sort (e.g. viewing a patient's appointment schedule, or accessing lab results) should only be allowed to request "read" scopes.
Remember that scopes are not user permissions, they are app permissions. For example, a clinician user might have read/write permissions against the CDR, but if an app they are using has only been granted read permissions, the app can not write data on the user's behalf.
A further decision to be made is around whether to prompt the user for scope approval. Smile CDR fully supports allowing the user to see which scopes an app is requesting, and make a decision about whether to approve them. Internet users are familiar with this workflow, as it is often seen when granting consumer internet apps permissions to see a user's profile information via common "Login with Google" and "Login with Facebook" type flows. This scope approval flow happens on the OpenID Connect Approval Page (sometimes called the "Consent Screen").
The explicit user approval step is not mandatory however. Scopes may be configured as Auto-Approve in the Smile CDR Client Definition screen if an organization wishes to approve these scopes automatically without showing them to the user. This might be done for enterprise apps where the app is fully trusted, or in cases where the app has been vetted by an organization and is believed to not require user permissions. Scopes can even be configured as Auto-Grant, which means that they will be granted any time a successful authentication occurs, even if the application did not request them.
Scopes are short strings of text, with no whitespace in them. For example in SMART on FHIR, the patient/Encounter.read
scope can be used by an application to request access to access Encounter resources belonging to the currently authorized Patient. A user-visible text description of SMART on FHIR scopes can be customized as well.
Smile CDR implements partial support for the search parameter resource constraints defined in SMART version 2.0. Read permissions can be narrowed by a FHIR query. For example, the scope patient/Observation.rs?category=laboratory
requests access to Observations, but only those categorized as laboratory results.
To use clinical scopes with filters, the Consent Service must be active in the FHIR endpoint. These filters are supported in the RDBMS and Mongo persistence modules. If SMART 2.0 search parameter filters are used while the Consent Service is not active, operations that attempt to return the filtered resources will fail with a 403 Forbidden
error.
Clinical scope filters are not currently supported in the FHIR Gateway or Hybrid Providers modules.
In many cases an app wants to be authorized without knowing which patient it is expected to display.
For example, consider a 3rd party Mobile App that is authorizing itself against a hospital Patient Portal that acts as both a SMART on FHIR compliant Authorization Server and Resource Server. Users already have an account (with username and password) in the Patient Portal application, and by authorizing against those credentials the Mobile App can download data from the Patient Portal.
In this case the user is associated with a Patient resource ID on the Resource Server, but neither the Mobile App nor the user are aware of what resource ID that would be.
Another use-case for launch scopes would be a portal which functions as a "desktop" of micro-applications (small web applications that do one thing, and do it well).
In this case, it could be assumed that the portal itself has a concept of which Patient (and/or which practitioner, which location, etc) is currently selected. The user's expectation would be that if a Patient is selected in the portal, any micro-apps would launch with the same Patient selected.
SMART on FHIR specifies a set of scopes which request that the Authorization Server return the launch context to the Client. These scopes are named launch/[type], where [type] is one of patient
, location
, practitioner
, or another type of your choosing.
From the Client's perspective, a flow using a launch scope works as follows:
launch/patient
scope. This is a request for the Authorization Server to include a patient launch context.access_token
and id_token
are received, the response will contain an additional claim called patient
.{
"access_token":"eyJraWQiOiJ1bml0dGV (..snipped..)",
"token_type":"bearer",
"scope":"launch/patient openid profile",
"id_token":"eyJraWQiOiJ1bml0dGVzdC1 (..snipped..)",
"patient":"123"
}
"patient": "123"
claim.The openid
and profile
scopes are used by the Client to request user identity information from the Authorization Server as a part of the auth flow.
When these scopes are requested, an additional claim is returned by the authentication server called id_token
. This claim is called the ID Token, and you can see it being returned in many of the examples above.
The ID Token is a Signed JWT. It may be decoded, revealing a number of attributes.
{
"jti":"d34af0de-e65a-4c19-8e38-365916948356"
"at_hash":"xDKTbwLJPrb6hr_loWD8Qw",
"sub":"user123",
"profile":"http:\/\/example.com\/issuer\/fhir\/RelatedPerson\/123",
"iss":"http:\/\/example.com\/issuer",
"nonce":"NONCETEXT-123",
"aud":"my-client-id",
"auth_time":1532416414,
"exp":1532416474,
"iat":1532416414,
"name":"John Smith",
"given_name":"John",
"family_name":"Smith",
}
The authorization token contains a fhirContext
collection of launch context references.
Such data is generally used to provide context to Smart Apps about the patient/resources in question, for example, about a PractitionerRole being relevant to the present request state.
This is stored in the token as a JSON array.
A given component has a reference/role pair, which the role being optional, so such a component can be either:
A fhirContext may be empty if no such components were added (ex: "fhirContext": []
)
See the fhir standard for more details: https://hl7.org/fhir/smart-app-launch/scopes-and-launch-context.html#fhir-context
{
"sub": "ADMIN",
"iss": "http://localhost:9001",
"smile_cdr_module_id": "smart_auth",
"token_type": "Bearer",
"nbf": 1689600677,
"azp": "my-client-id-reftoken",
"scope": "online_access openid patient/*.read",
"fhirContext": [{"reference":"Provenance/xxx"},{"reference":"Schedule/yyy"},{"reference":"Schedule/zzz"},{"reference":"PractitionerRole/123"},{"reference":"List/456","role":"https://example.org/med-list-at-home"}],
"smile_cdr_node_id": "unit_test_node",
"exp": 1689601277,
"iat": 1689600677,
"jti": "0d0fa5c9-00ce-459c-95d0-24c7a2bf167a"
}