On this page:

7.15SMART on FHIR Inbound Security Module

 

The SMART Inbound Security module is a security module that authorizes client requests by validating OpenID Connect Access Tokens.

Unlike the SMART on FHIR Outbound Security Module, this module is not itself an Authorization Server; rather it assumes the existence of an external Authorization Server and validates that Access Tokens were granted by that server.

7.15.1Architecture

 

The following diagram shows the flow when using the SMART Inbound Security module to perform authorization.

SMART Inbound Security Flow

7.15.2Requirements

 

This module requires the existence of an external Authorization Server that is compliant with the SMART on FHIR Authorization Guide. Specifically, this external Authorization Server must be a compliant OpenID Connect server.

In particular, the authorization server must have the following properties:

  • The server must generate Access Tokens that are signed JSON Web Tokens (JWT) according to the OpenID Connect specification.
  • These signed tokens must contain valid claims:
    • iss - Issuer
    • sub - Subject
    • exp - Expiry (optional)
  • The server must support token introspection using a Client ID and Client Secret as defined in the OpenID Connect specification.

The external Authorization Server could be a separate installation of Smile CDR using a SMART Outbound Security module but it could also be a different product such as Keycloak, MitreID Connect, IdentityServer, etc.

7.15.3Setting Up This Module

 

Setting up a SMART on FHIR architecture with an external Authorization Server requires both an inbound security module and one or more OpenID Connect server definitions.

Creating the Module

To create a SMART Inbound Security module in the Web Admin Console:

  • Navigate to Config -> Module Config
  • Select the SMART Inbound Security module type and click Add
  • Configure the module and click Save

The SMART Inbound Security module does not require much configuration, as the process of accepting a SMART Access Token is a fairly rigid process. The module has two properties for defining endpoint URLs: Token Endpoint and Authorization Endpoint.

These properties do not actually affect the behaviour of the inbound security module, but they will affect the CapabilityStatement exported by any FHIR endpoints that use the module.

7.15.4Accepting Tokens

 

The SMART Inbound Security module can authorize tokens in two ways:

Accepting Internal Access Tokens

7.15.5Accepting Internal Access Tokens

 

In a single-node deployment, or a cluster with only one master node (see Designing A Cluster) the SMART Outbound Security module is able to act as an OpenID Connect Authentication dependency to other modules (e.g. FHIR Endpoint).

In more complex setups however, a SMART Outbound Security module may be located on a different master node. In this case, a SMART Inbound Security module must be placed on the same master node as any modules that need to accept tokens issued by the SMART Outbound Security module Authorization Server. See Multi-Master Clusters for information on designing a cluster with multiple master nodes.

In this configuration, the SMART Inbound Security module should have the trust_intra_cluster_tokens.modules property set with a value pointing to the SMART Outbound Security module on its master node.

7.15.6Accepting External Access Tokens

 

When validating an external access token (a token that was issued by a SMART on FHIR compliant Authorization Server), Smile CDR will validate the token using the standard OpenID Connect mechanisms. This involves parsing the signed JWT access token, verifying the signature, granting the user appropriate permissions, and finally applying scopes to the user session.

Validating Tokens

When accepting external tokens, Smile CDR will first validate the token to ensure that it has been digitally signed with a key that is trusted. This involves the following steps inside the SMART Inbound Security module:

  • The access token is decoded as a signed JWT so that it can be inspected.
  • The issuer field is extracted in the token in order to match it with a server definition in Smile CDR.
  • SMART Inbound Security module searches for a server definition that matches the issuer URL. Note that only server definitions for the given SMART Inbound Security module are searched.
  • The server locates the key needed to verify the signature:
    • If the server definition has an explicit key defined, it is used to verify the token.
    • If the server definition does not have an explicit key defined, the SMART Inbound Security module makes a request to the issuer URL discovery document to request the public key of the server. Note that this strategy can be beneficial because it can account for keys that are rotated regularly without needing to manually update configuration. However, it also means that the Smile CDR server must always be able to communicate with the OpenID Connect server. This strategy does not work for signature algorithms that require a shared secret key, such as HS256.

See JWT Signature Algorithms for a list of supported algorithms that can be used to verify a signed token.

Assigning Permissions

In order to accept tokens from an external source, a callback script should generally be supplied. This script will process the verified access token and assign the user an appropriate set of permissions based on this token (and the scopes and claims it provides).

It is important to realize that a token with a set of scopes is not enough on its own to determine what a user may do. For example, a session that has been granted Patient/*.read permission has read access to all resource types for some patient, but which patient(s) that is is not explicitly stated in the token. This is why a script is required. The script is configured in the SMART Inbound Security module configuration, and much contain an onAuthenticateSuccess(...) function that will be called immediately after the OIDC token is validated, but before it is turned into a Smile CDR session.

Example: Simple Testing Script

The following example simply assigns all permissions to the user. This might be appropriate if all users of an application should have access to all data.

/**
 * This is a sample authentication callback script for the
 * SMART Inbound Security module.
 *
 * @param theOutcome The outcome object. This contains details about the user that was created
 * 	in response to the incoming token.
 * 	
 * @param theOutcomeFactory A factory object that can be used to create a new success or failure
 * 	object
 * 	
 * @param theContext The login context. This object contains details about the authorized
 * 	scopes and claims. Because this script will be used with the SMART Inbound Security
 * 	module, the type for this parameter will be of type SecurityInSmartAuthenticationContext,
 * 	which is described here: 
 * 	https://try.smilecdr.com/docs/javascript_execution_environment/callback_models.html#securityinsmartauthenticationcontext
 * 	
 * @returns {*} Either a successful outcome, or a failure outcome
 */
function onAuthenticateSuccess(theOutcome, theOutcomeFactory, theContext) {

	// We will grant the user ROLE_FHIR_CLIENT_SUPERUSER which gives them access to all 
	// FHIR data. Note that the list of approved scopes may cause the permissions assigned
	// here to be reduced automatically when the session is created.
	theOutcome.addAuthority('ROLE_FHIR_CLIENT_SUPERUSER');

	return theOutcome;
}

Example: Patient Application

The following example assumes that an external OIDC server has been configured to issue access tokens that will be consumed by the SMART Inbound Security module. In order to convey the identity of the authorized patient, the OIDC server in this example has been configured to include a claim called patient, which will include the resource ID for the Patient resource corresponding to the authenticated user.

Consider the following decoded access token:

{
  "sub": "myusername",
  "azp": "my-client-id",
  "patient": "123",
  "scope": "openid profile patient/*.read",
  "iss": "http://example.com/oidc-issuer"
  // .... other claims omitted ....
}

A callback script which is able to apply the patient claim is shown below:

/**
 * This is a sample authentication callback script for the
 * SMART Inbound Security module.
 *
 * @param theOutcome The outcome object. This contains details about the user that was created
 * 	in response to the incoming token.
 * 	
 * @param theOutcomeFactory A factory object that can be used to create a new success or failure
 * 	object
 * 	
 * @param theContext The login context. This object contains details about the authorized
 * 	scopes and claims. Because this script will be used with the SMART Inbound Security
 * 	module, the type for this parameter will be of type SecurityInSmartAuthenticationContext,
 * 	which is described here: 
 * 	https://try.smilecdr.com/docs/javascript_execution_environment/callback_models.html#securityinsmartauthenticationcontext
 * 	
 * @returns {*} Either a successful outcome, or a failure outcome
 */
function onAuthenticateSuccess(theOutcome, theOutcomeFactory, theContext) {

	// We expect the access token JWT to have a claim called "patient" with
	// a value such as "123". This claim is assumed to be the ID of the patient
	// for whom the token was issued.
	var patientClaim = theContext.getStringClaim('patient');
	if (!patientClaim) {
		throw 'No claim "patient" in access token';
	}
	var patientId = 'Patient/' + patientClaim;

	// Log a bit of troubleshooting information
	Log.info("User " + theOutcome.getUsername() + " has authorized for " + patientId + " with scopes: " + theContext.getApprovedScopes());

	// We will grant the user the ability to perform the FHIR /metadata operation,
	// as well as giving them read/write permission to the Patient record corresponding
	// to their account. Note that the list of approved scopes may cause this to be
	// reduced automatically. E.g. If the user denies the "patient/*.write" scope,
	// the FHIR_WRITE_ALL_IN_COMPARTMENT will automatically be stripped from the 
	// session even though it is granted here.
	theOutcome.addAuthority('FHIR_CAPABILITIES');
	theOutcome.addAuthority('FHIR_READ_ALL_IN_COMPARTMENT', patientId);
	theOutcome.addAuthority('FHIR_WRITE_ALL_IN_COMPARTMENT', patientId);

	return theOutcome;
}

7.15.7Creating a Server Definition

 

An OpenID Connect Server definition must be created in order to accept external access tokens. This definition describes the external Authorization Server and tells the SMART Inbound Security module that it is okay to trust the Authorization Server and the tokens it generates in order to authorize access to functions in Smile CDR.

To create an OpenID Connect Server definition:

This definition has a few properties:

Issuer

The most important property when defining the server definition is the Issuer: This is a URL which points to the base URL of the OpenID Connect Authorization Server you wish to trust. Note that the value here must point to a valid OpenID Connect server, and this server must be network accessible by the SMART Inbound Security module. The issuer URL must also match the iss (issuer) claim that is received as a part of any Access Tokens created by the Authorization Server.

Note that trailing slashes in the Issuer URL are ignored in order to avoid incompatibilities. If the Authorization Server iss claim includes a trailing slash, this will not cause any issues.

Key

If present, the validation key is specified in JWK/JWKS format. Any tokens accepted for this server will be verified against the given key, and authentication will fail if the token can not be verified.