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.
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.
The following diagram shows the flow when using the SMART Inbound Security module to perform authorization.
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.
To create a SMART Inbound Security module in the Web Admin Console:
Module Config
Module Config
SMART Inbound Security
module type and click Add
Save
The SMART Inbound Security module does not require much configuration, as the process of accepting a SMART Access Token is a fairly well-defined 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.
The SMART Inbound Security module can authorize tokens in two ways:
In a deployment where Smile CDR is acting as the OIDC Provider using the SMART Outbound Security module, the outbound security module is able to act as an OpenID Connect Authentication dependency to other modules (e.g. FHIR Endpoint).
In order to achieve separation of concerns, and to scale different parts of the architecture separately, the SMART Outbound Security module can be placed on a different node from the services being secured (e.g. FHIR Endpoint). In this model, a SMART Inbound Security module should be placed on the same node as the services being secured, and 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 node.
When validating an external access token (a token that was issued by a third-party 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.
If possible, the third party server should be configured to support the JSON Web Token (JWT) Profile for OAuth2 2.0 Access Tokens. This profile specifies that Access Tokens must be signed JWTs containing at least the following claims:
iss
- Issuersub
- Subjectexp
- Expiry (optional)The server must support token introspection using a Client ID and Client Secret as defined in the OpenID Connect specification.
The following diagram shows the internal flow used by the SMART on FHIR Inbound Security module to process Access 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:
See JWT Signature Algorithms for a list of supported algorithms that can be used to verify a signed token.
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:
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.
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.
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.
Several examples of this function can be found in Callback Scripts: onAuthenticateSuccess
If the Authentication Server issues tokens that are not signed JWTs, an authenticate()
function must be supplied instead of the onAuthenticateSuccess()
function. This function is responsible for validating the token and determining whether it is valid.
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 method is called by the SMART Inbound Security module in order to validate the
* received Access Token and either generate a successful authentication, or
* to reject it.
*
* @param theRequest The incoming theRequest
* @param theOutcomeFactory This object is a factory for a successful
* response or a failure response
* @returns {*}
*/
function authenticate(theRequest, theOutcomeFactory) {
// The received access token is stored in the password field in the
// request object
let token = theRequest.getPassword();
// In this example, we'll look for a hardcoded token value. In a real scenario, something more
// advanced will be needed
let failed = (token === 'ABCDEFG123455');
if (failed) {
let outcome = theOutcomeFactory.newFailure();
failure.message = 'Token is invalid';
return failure;
}
// Otherwise, prepare a valid response
let outcome = theOutcomeFactory.newSuccess();
outcome.username = theRequest.getUsername();
outcome.addAuthority('FHIR_READ_ALL_IN_COMPARTMENT', 'Patient/123');
outcome.addApprovedScope('patient/*.read');
return outcome;
}
By default, issuer and subject access token claims are used to create Smile CDR username for the external user. This function allows to override this behaviour.
/**
* This is a sample username mapping callback script
*
* @param theOidcUserInfoMap OIDC claims from the Access token as a map
*
* @param theServerInfo JSON mapping of the OAuth server definition (backed by ca.cdr.api.model.json.OAuth2ServerJson)
*
* @returns Local unique Smile CDR username for the external user.
*/
function getUserName(theOidcUserInfoMap, theServerInfo) {
return "EXT_USER:" + theOidcUserInfoMap['preferred_username'];
}
Called after the session's authorities have been narrowed based on the approved scopes. This function can be used to restore any removed authorities, or add new ones based on approved scopes or permissions. If Enforce Approved Scopes to Restrict Permissions is not enabled, this function can be used to manually apply scope-based authority narrowing logic in place of the built-in logic.
Further documentation and an example can be found in Callback Scripts: onSmartScopeAuthorityNarrowing