Access control and SMART on FHIR

Concepts

This explanation of access control and SMART on FHIR in Firely Server requires basic understanding of the architecture of Firely Server, so you know what is meant by middleware components and repository interfaces. It also presumes general knowledge about authentication and OAuth2.

Access control generally consists of the following parts, which will be addressed one by one:

  • Identification: Who are you? – usually a user name, login, or some identifier.

  • Authentication: Prove your identification – usually with a password, a certificate or some other (combination of) secret(s) owned by you.

  • Authorization: What are you allowed to read or change based on your identification?

  • Access Control Engine: Enforce the authorization in the context of a specific request.

Note that Firely offers Firely Auth as an OAuth2 provider that is optimized to use in the context of SMART on FHIR.

Identification and Authentication

Firely Server does not authenticate users itself. It is meant to be used in an OAuth2 environment in which an OAuth2 provider is responsible for the identification and authentication of users. Typically, a user first enters a Web Application, e.g. a patient portal, or a mobile app. That application interactively redirects the user to the OAuth2 provider - which is the authorization server and may or may not handle authentication as well - to authenticate, and receives an OAuth2 token back. Then, the application can do an http request to Firely Server to send or receive resource(s), and provide the OAuth2 token in the http Authentication header, thereby acting on behalf of the user. Firely Server can then read the OAuth2 token and validate it with the OAuth2 authorization server. This functionality is not FHIR specific.

Authorization

Authorization in Firely Server by default is based on SMART on FHIR and more specifically the Scopes and Launch Context defined by it. The SMART specification is released in two different version as of the date of publication: SMART v1 and SMART v2. Both versions define a syntax for expressing “scope”-claims within an access token, as well as providing for specialized claims for conveying context information (e.g. patient launch context claims).

These are examples of scopes and launch contexts that are recognized by Firely Server (SMART v1):

  • scope=user/Observation.r: the user is allowed to read Observation resources

  • scope=user/Encounter.crud: the user is allowed to read, create, update and delete Encounter resources

  • scope=user/*.rs: the user is allowed to read and search any type of resource

  • scope=user/*.cud: the user is allowed to create, update and delete any type of resource

  • scope=[array of individual scopes]

  • patient=123: the user is allowed access to resources in the compartment of patient 123 – see Compartments.

All scopes using SMART v1 can also be expressed in SMART v2:

  • scope=user/Observation.r: the user is allowed to read Observation resources

  • scope=user/Encounter.cu: the user is allowed to write (create and update) Encounter resources

  • scope=user/*.r: the user is allowed to read any type of resource

  • scope=user/*.cu: the user is allowed to write (create and update) any type of resource

  • scope=[array of individual scopes]

  • patient=123: the user is allowed access to resources in the compartment of patient 123 – see Compartments.

See section Tokens for detailed requirements regarding the structure of the access token in order to enable Firely Server to enforce these scopes.

Firely Server fully supports the syntax of SMART v1: ( 'patient' | 'user' ) '/' ( fhir-resource | '*' ) '.' ( 'read' | 'write' | '*' )

Additionally, the syntax of SMART v2 scopes is fully supported: ( 'patient' | 'user' | 'system' ) '/' ( fhir-resource | '*' ) '.' ( 'c' | 'r' | 'u' | 'd' | 's' | '*') ? param = value

All search capabilities supported by Firely Server can also be evaluated as part of the access scope using SMART v2. Chaining and Reverse Chaining is explicitly supported here:

  • scope=user/Observation.r?category=laboratory: the user is allowed to read Observation resources with a category element containing the code “laboratory”

  • scope=user/*.rs?_tag=http://example.org/fhir/sid/codes|tag: the user is allowed to read and search all resource containing a tag in Meta.tag with system “http://example.org/fhir/sid/codes” and code “tag”

  • scope=user/Observation.rs?encounter.id=Encounter/test: the user is allowed to see all Observation resources linked to the Encounter with id “123”.

SMART on FHIR also defines scopes starting with ‘patient/’ instead of ‘user/’. In Firely Server these are evaluated differently. With a scope of ‘patient/’ you are required to also have a ‘patient=…’ launch context to know to which patient the user connects. As mentioned above, any request is scoped to the patient compartment and requests are rejected if the patient claim is not provided in the access token. Firely Server will additionally handle user-level scopes by checking the syntax of the SMART on FHIR scopes within the access token. It enforces that only allowed resources types are accessed and only allowed actions are executed.

Attention

Requests using a user-level scope are not limited a pre-defined context, e.g. a Patient compartment. Therefore all matching resources are returned to the client. It is highly advised to implement additional security measures using a custom plugin, e.g. by enforcing a certain Practitioner or Encounter context.

SMART on FHIR scopes are used to:

  • configure a client: which scopes can it request?

  • request authorization: client requests a set of scopes

  • consent: user consents to the client using the requested scopes

  • access token: Firely Server can read from the access token which scopes the client is granted (an intersection of the three above)

Access Control Engine

The Access Control Engine in Firely Server evaluates two types of authorization:

  1. Type-Access: Is the user allowed to read or write resource(s) of a specific resourcetype?

  2. Compartment: Is the data to be read or written within the current compartment (if any)?

As you may have noticed, Type-Access aligns with the concept of scopes, and Compartment aligns with the concept of launch context in SMART on FHIR.

The Firely Server SmartContextMiddleware component extracts the claims defined by SMART on FHIR from the OAuth2 token, and puts it into two classes that are then available for Access Control Decisions in all the interaction handlers (e.g. for read, search, create etc.)

SMART on FHIR defines launch contexts for Patient, Encounter and Location, extendible with others if needed. If a request is done with a Patient launch context, and the user is allowed to see other resource types as well, these other resource types are restricted by the Patient CompartmentDefinition.

Note

To enable access to additional resources (outside the compartment), the client must request additional scopes.

Custom Authentication

You may build a plugin with custom middleware to provide authentication in a form that suits your needs. One example could be that you want to integrate ASP.NET Core Identity into Firely Server. Then you don’t need the OAuth2 middleware, but instead can use the Identity framework to authenticate your users. See Custom authorization plugin for more details.

Other forms of Authorization

In Access Control in Plugins and Facades you can find the interfaces relevant to authorization in Firely Server. If your environment requires other authorization information than the standard SMART on FHIR claims, you can create your own implementations of these interfaces. You do this by implementing a custom plugin. All the standard plugins of Firely Server can then use that implementation to enforce access control.

Configuration

You will need to add the Smart plugin to the Firely Server pipeline. See Firely Server Custom Plugins for more information. In appsettings[.instance].json, locate the pipeline configuration in the PipelineOptions section, or copy that section from appsettings.default.json (see also Changing the settings):

"PipelineOptions": {
  "PluginDirectory": "./plugins",
  "Branches": [
        {
          "Path": "/",
          "Include": [
                "Vonk.Core",
                "Vonk.Fhir.R3",
                ...

Add Vonk.Smart (for SMART v1) or Vonk.Plugin.SoFv2 (for SMART v2) to the list of included plugins. When you restart Firely Server, the Smart service will be added to the pipeline. An error will be thrown if both plugins are part of the pipeline. Please note that the SMART v2 plugin will allow the usage of the SMART v1 and SMART v2 syntax.

You can control the way Access Control based on SMART on FHIR behaves with the SmartAuthorizationOptions in the Firely Server settings:

"SmartAuthorizationOptions": {
  "Enabled": true,
  "Filters": [
    {
      "FilterType": "Patient", //Filter on a Patient compartment if a 'patient' launch scope is in the auth token
      "FilterArgument": "identifier=#patient#" //... for the Patient that has an identifier matching the value of that 'patient' launch scope
    }
    //{
    //  "FilterType": "Encounter", //Filter on an Encounter compartment if an 'encounter' launch scope is in the auth token
    //  "FilterArgument": "identifier=#encounter#" //... for the Encounter that has an identifier matching the value of that 'encounter' launch scope
    //},
    //{
    //  "FilterType": "RelatedPerson", //Filter on a RelatedPerson compartment if a 'relatedperson' launch scope is in the auth token
    //  "FilterArgument": "identifier=#relatedperson#" //... for the RelatedPerson that has an identifier matching the value of that 'relatedperson' launch scope
    //},
    //{
    //  "FilterType": "Practitioner", //Filter on a Practitioner compartment if a 'practitioner' launch scope is in the auth token
    //  "FilterArgument": "identifier=#practitioner#" //... for the Practitioner that has an identifier matching the value of that 'practitioner' launch scope
    //},
    //{
    //  "FilterType": "Device", //Filter on a Device compartment if a 'device' launch scope is in the auth token
    //  "FilterArgument": "identifier=#device#" //... for the Device that has an identifier matching the value of that 'device' launch scope
    //}
  ],
  "Authority": "url-to-your-identity-provider",
//"AdditionalBaseEndpointsInDiscoveryDocument": ["additional-url-to-your-identity-provider"],
//"AdditionalIssuersInToken": ["additional-url-to-your-identity-provider"],
  "Audience": "name-of-your-fhir-server" //Default this is empty
//"ClaimsNamespace": "http://smarthealthit.org",
  "RequireHttpsToProvider": false, //You want this set to true (the default) in a production environment!
  "Protected": {
    "InstanceLevelInteractions": "read, vread, update, delete, history, conditional_delete, conditional_update, $validate",
    "TypeLevelInteractions": "create, search, history, conditional_create",
    "WholeSystemInteractions": "batch, transaction, history, search"
  },
  "TokenIntrospection": {
    "ClientId": "Firely Server",
    "ClientSecret": "secret"
  },
  "ShowAuthorizationPII": false,
//"AccessTokenScopeReplace": "-",
  "SmartCapabilities": [
    "LaunchStandalone",
    "LaunchEhr",
    //"AuthorizePost",
    "ClientPublic",
    "ClientConfidentialSymmetric",
    //"ClientConfidentialAsymmetric",
    "SsoOpenidConnect",
    "ContextStandalonePatient",
    "ContextStandaloneEncounter",
    "ContextEhrPatient",
    "ContextEhrEncounter",
    "PermissionPatient",
    "PermissionUser",
    "PermissionOffline",
    "PermissionOnline",
    "PermissionV1",
    //"PermissionV2",
    "ContextStyle",
    "ContextBanner"
  ]
}
  • Enabled: With this setting you can disable (‘false’) the authentication and authorization altogether. When it is enabled (‘true’), Firely Server will also evaluate the other settings. The default value is ‘false’. This implies that authorization is disabled as if no SmartAuthorizationOptions section is present in the settings.

  • Filters: Defines how different launch contexts are translated to search arguments. See Compartments for more background.

    • FilterType: Both a launch context and a CompartmentDefinition are defined by a resourcetype. Use FilterType to define for which launch context and related CompartmentDefinition this Filter is applicable.

    • FilterArgument: Translates the value of the launch context to a search argument. You can use any supported search parameter defined on FilterType. It should contain the name of the launch context enclosed in hashes (e.g. #patient#), which is substituted by the value of the claim.

  • Authority: The base url of your identity provider, such that {{base_url}}/.well-known/openid-configuration returns a valid configuration response (OpenID Connect Discovery documentation). At minimum, the jwks_uri, token_endpoint and authorization_endpoint keys are required in addition to the keys required by the specification. See Firely Auth for more background.

  • AdditionalBaseEndpointsInDiscoveryDocument: Optional configuration setting. Add additional base authority endpoints that your identity provider also uses for operations that are listed in the .well-known document.

  • AdditionalIssuersInToken: Optional configuration setting. The additional issuer setting will extend the list of issuer urls that are valid within the issuer claim in the token passed to Firely Server. The token validation will be adjusted accordingly. Please note that it does not influence which issuer urls are allowed in the .well-known/openid-configuration document of the authorization server.

  • Audience: Defines the name of this Firely Server instance as it is known to the Authorization server. Default is ‘firelyserver’.

  • ClaimsNamespace: Some authorization providers will prefix all their claims with a namespace, e.g. http://my.company.org/auth/user/*.read. Configure the namespace here to make Firely Server interpret it correctly. It will then strip off that prefix and interpret it as just user/*.read. By default no namespace is configured.

  • RequireHttpsToProvider: Token exchange with an Authorization server should always happen over https. However, in a local testing scenario you may need to use http. Then you can set this to ‘false’. The default value is ‘true’.

  • Protected: This setting controls which of the interactions actually require authentication. In the example values provided here, $validate is not in the TypeLevelInteractions. This means that you can use POST [base-url]/Patient/$validate without authorization. Since you only read Conformance resources with this interaction, this might make sense.

  • TokenIntrospection: This setting is configurable when you use reference tokens.

  • ShowAuthorizationPII: This is a flag to indicate whether or not personally identifiable information is shown in logs.

  • AccessTokenScopeReplace: With this optional setting you tell Firely Server which character replaces the / (forward slash) character in a SMART scope. This setting is needed in cases like working with Azure Active Directory (see details in section Azure Active Directory).

  • SmartCapabilities: This setting can be used to configure SMART capabilities. All capabilities listed here are supported by Firely Server, you can enable/disable specific capabilities based on your authorization server implementation.

Note

After properly configuring Firely Server to work with an OAuth2 authorization server, enabling SMART and configuring the SmartCapabilities for Firely Server, you are able to discover the SMART configuration metadata by retrieving <base-url>/.well-known/smart-configuration.

Please check section Retrieve .well-known/smart-configuration in the SMART specification for more details on how to request the metadata and how to interpret the response.

Compartments

In FHIR a CompartmentDefinition defines a set of resources ‘around’ a focus resource. For each type of resource that is linked to the focus resource, it defines the reference search parameters that connect the two together. The type of the focus-resource is in CompartmentDefinition.code, and the relations are in CompartmentDefinition.resource. The values for param in it can be read as a reverse chain.

An example is the Patient CompartmentDefinition, where a Patient resource is the focus. One of the related resourcetypes is Observation. Its params are subject and performer, so it is in the compartment of a specific Patient if that Patient is either the subject or the performer of the Observation.

FHIR defines CompartmentDefinitions for Patient, Encounter, RelatedPerson, Practitioner and Device. Although Firely Server is functionally not limited to these five, the specification does not allow you to define your own. Firely Server will use a CompartmentDefinition if:

  • the CompartmentDefinition is known to Firely Server, see Managing Conformance Resources for options to provide them.

  • the OAuth2 Token contains a claim with the same name as the CompartmentDefinition.code (but it must be lowercase).

So some of the launch contexts mentioned in SMART on FHIR map to CompartmentDefinitions. For example, the launch context ‘launch/patient’ and ‘launch/encounter’ map to the compartment ‘Patient’ and ‘Encounter’. Please note that launch contexts can be extended for any resource type, but not all resource types have a matching CompartmentDefinition, e.g. ‘Location’.

A CompartmentDefinition defines the relationships, but it becomes useful once you combine it with a way of specifying the actual focus resource. In SMART on FHIR, the launch context can do that, e.g. patient=123. As per the SMART Scopes and Launch Context, the value ‘123’ is the value of the Patient.id. Together with the Patient CompartmentDefinition this defines a – what we call – Compartment in Firely Server:

  • Patient with id ‘123’

  • And all resources that link to that patient according to the Patient CompartmentDefinition.

There may be cases where the logical id of the focus resource is not known to the authorization server. Let’s assume it does know one of the Identifiers of a Patient. The Filters in the Configuration allow you to configure Firely Server to use the identifier search parameter as a filter instead of _id. The value in the configuration example does exactly that:

"Filters": [
  {
    "FilterType": "Patient", //Filter on a Patient compartment if a 'patient' launch scope is in the auth token
    "FilterArgument": "identifier=#patient#" //... for the Patient that has an identifier matching the value of that 'patient' launch scope
  },
  ...
]

Please notice that it is possible that more than one Patient matches the filter. This is intended behaviour of Firely Server, and it is up to you to configure a search parameter that is guaranteed to have unique values for each Patient if you need that. You can always stick to the SMART on FHIR default of _id by specifying that as the filter:

"Filters": [
  {
    "FilterType": "Patient", //Filter on a Patient compartment if a 'patient' launch scope is in the auth token
    "FilterArgument": "_id=#patient#" //... for the Patient that has an identifier matching the value of that 'patient' launch scope
  },
  ...
]

But you can also take advantage of it and allow access only to the patients from a certain General Practitioner, of whom you happen to know the Identifier:

"Filters": [
  {
    "FilterType": "Patient", //Filter on a Patient compartment if a 'patient' launch scope is in the auth token
    "FilterArgument": "general-practitioner.identifier=#patient#" //... for the Patient that has an identifier matching the value of that 'patient' launch scope
  },
  ...
]

In this example the claim is still called ‘patient’, although it contains an Identifier of a General Practitioner. This is because the CompartmentDefinition is selected by matching its code to the name of the claim, regardless of the value the claim contains.

If multiple resources match the Compartment, that is no problem for Firely Server. You can simply configure the Filters according to the business rules in your organization.

Tokens

When a client application wants to access data in Firely Server on behalf of its user, it requests a token from the authorization server (configured as the Authority in the Configuration). The configuration of the authorization server determines which claims are available for a certain user, and also for the client application. The client app configuration determines which claims it needs. During the token request, the user is usually redirected to the authorization server, which might or might not be the authentication server as well, logs in and is then asked whether the client app is allowed to receive the requested claims. The client app cannot request any claims that are not available to that application. And it will never get any claims that are not available to the user. This flow is also explained in the SMART App Authorization Guide.

The result of this flow should be a JSON Web Token (JWT) containing zero or more of the claims defined in SMART on FHIR. The claims can either be scopes or a launch context, as in the examples listed in Authorization. This token is encoded as a string, and must be sent to Firely Server in the Authorization header of the request.

A valid access token for Firely Server at minimum will have:

  • the iss claim with the base url of the OAuth server

  • the aud the same value you’ve entered in SmartAuthorizationOptions.Audience

  • the scope field with the scopes granted by this access token

  • optionally, the compartment claim, if you’d like to limit this token to a certain compartment. Such a claim may be requested by using a context scope or launching a part of an EHR launch. See Requesting context with scopes for more details. For example, in case of Patient data access where the launch/patient scope is used, include the patient claim with the patient’s id or identifier (Compartments) and make sure to request the patient/<permissions> scope permissions. Compartment claims combined with user/<permissions> claims are not taken into account.

Warning

Firely Server will not enforce any access control for resources outside of the specified compartment. Some compartment definitions do not include crucial resource types like ‘Patient’ or their corresponding resource type, i.e. all resources of this type regardless of any claims in the access token will be returned if requested. Please use this feature with caution! Additional custom access control is highly recommended.

Permissions (AccessPolicy)

Firely Server supports filtering the granted access scopes for a client by using built-in custom access policy resources. The access policy decisions are based on HL7 SMART on FHIR scopes, both SMART on FHIR v1 and v2 scopes are supported. The structure definitions are preloaded in Firely Server and can be viewed on the administration endpoint API or in Simplifier under Firely Server Definitions - Access Policy (R4) . The AccessPolicyDefinition resource controls the scopes which are permissible. The AccessPolicy resource contains the references to Patient, Group, Practitioner, PractitionerRole, Person, RelatedPerson and Device for which the AccessPolicyDefinition applies. If a reference (Patient, Group, …) is not referenced by an AccessPolicy, the requested scopes are granted without filtering.

Note

This functionality is a restrictive filter; it does not give additional scope permissions that were not requested by the client. All previous documentation about compartments and filter argument settings still applies.

Filter Logic Examples:

Requested Scopes

AccessPolicy Scopes

Resulting Scope Permissions

user/Patient.cr

user/Patient.r

user/Patient.r

user/Patient.*

user/Patient.r

user/Patient.r

user/Patient.c

user/Patient.r

no permission

user/*.r

user/Patient.*

user/Patient.r

user/Device.cr,

user/DiagnosticReport.c

user/Device.r,

user/DiagnosticReport.r,

user/Patient.r

user/Device.r

user/Device.crd,

user/DiagnosticReport.r,

user/Patient.d

user/*.cru

user/Device.cr,

user/DiagnosticReport.r

Policy Creation Example:

  1. Create an AccessPolicyDefinition resource on the administrative endpoint:

PUT {{BASE_URL}}/administration/AccessPolicyDefinition/UserReadsPatients

{
    "resourceType": "AccessPolicyDefinition",
    "id": "UserReadsPatients",
    "url": "https://fire.ly/fhir/AccessPolicyDefinition/UserReadsPatients",
    "version": "1.0.0",
    "name": "UserReadsPatients",
    "status": "active",
    "policy": [
        {
            "type": {
                "code": "smart-v1"
            },
            "restriction": [
                "user/Patient.read",
                "user/Observation.read"
            ]
        },
        {
            "type": {
                "code": "smart-v2"
            },
            "restriction": [
                "user/Patient.rs",
                "user/Observation.rs",
            ]
        }
    ]
}
  1. Next create an AccessPolicy resource on the repository endpoint:

PUT {{BASE_URL}}/AccessPolicy/AmbulatoryPractitioners

{
    "resourceType": "AccessPolicy",
    "id": "AmbulatoryPractitioners",
    "instantiatesCanonical": "https://fire.ly/fhir/AccessPolicyDefinition/UserReadsPatients",
    "subject": [
        {
            "reference": "Practitioner/Alice"
        },
        {
            "reference": "Practitioner/Bob"
        }
    ]
}

Note

Multiple AccessPolicy resources containing the same Reference will be combined. In the above example if the user Alice is found in another policy with user/Patient.c, the resulting permission will be user/Patient.crs

Firely Auth

Firely provides an optimized OAuth2 provider that understands SMART on FHIR scopes and the FHIR resource types they apply to out of the box. Along with the SoF specific launch claims and all the various client authentication flows. This product is called Firely Auth and can be acquired as part of Firely Server. You can also evaluate it using a Firely Server evaluation license. See Firely Auth for more information.

Azure Active Directory

Azure Active Directory (v2.0) does not allow to define a scope with / (forward slash) in it, which is not compatible with the structure of a SMART on FHIR scope. Therefore when you use AAD to provide SMART on FHIR scopes to Firely Server, you need to take the following steps

  1. In a SMART scope, use another character (for instance -) instead of /. For example:

  • user/*.read becomes user-*.read

  • user/*.write becomes user-*.write

  • patient/Observation.r becomes patient-Observation.r

If the used character (for instance -) is already in your SMART scope, then you can use \ (backward slash) to escape it.

  • patient/Observation.r?_id=Id-With-Dashes becomes patient-Observation.r?_id=Id\-With\-Dashes

If a \ (backward slash) is already in your SMART scope, then you can escape it with another \.

  • patient/Observation.r?_id=Id\With\BackwardSlash becomes patient-Observation.r?_id=Id\\With\\BackwardSlash

  1. Configure Firely Server which character is used in Step 1, then Firely Server will generate a proper SMART on FHIR scope and handle the request further. This can be configured via setting AccessTokenScopeReplace.

For the first step above, instead of doing it manually, you can deploy SMART on FHIR AAD Proxy to Azure, which helps you to replace / to - in a SMART scope when you request your access token. The other option would be to follow Quickstart: Deploy Azure API for FHIR using Azure portal, check “SMART on FHIR proxy” box and use the proxy by following Tutorial: Azure Active Directory SMART on FHIR proxy.

Warning

When you use the SMART on FHIR AAD Proxy, be careful with SMART on FHIR v2 scopes. - is an allowed character within the access scope (see examples below). In those cases, the proxy simply replaces / with - and does not escape the original -, then Firely Server cannot figure out which - is original, which will result in a failed request.

  • patient/Observation.rs?category=http://terminology.hl7.org/CodeSystem/observation-category|laboratory

  • Observation.rs?code:in=http://valueset.example.org/ValueSet/diabetes-codes

Access Control Decisions

In this paragraph we will explain how Access Control Decisions are made for the various FHIR interactions. For the examples assume a Patient Compartment with identifier=123 as filter.

  1. Search

    1. Direct search on compartment type

      Request:

      GET [base]/Patient?name=fred

      Type-Access:

      User must have read access to Patient, otherwise a 401 is returned.

      Compartment:

      If a Patient Compartment is active, the Filter from it will be added to the search, e.g. GET [base]/Patient?name=fred&identifier=123

    2. Search on type related to compartment

      Request:

      GET [base]/Observation?code=x89

      Type-Access:

      User must have read access to Observation, otherwise a 401 is returned.

      Compartment:

      If a Patient Compartment is active, the links from Observation to Patient will be added to the search. In pseudo code: GET [base]/Observation?code=x89& (subject:Patient.identifier=123 OR performer:Patient.identifier=123)

    3. Search on type not related to compartment

      Request:

      GET [base]/Organization

      Type-Access:

      User must have read access to Organization, otherwise a 401 is returned.

      Compartment:

      No compartment is applicable to Organization, so no further filters are applied.

    4. Search with include outside the compartment

      Request:

      GET [base]/Patient?_include=Patient:organization

      Type-Access:

      User must have read access to Patient, otherwise a 401 is returned. If the user has read access to Organization, the _include is evaluated. Otherwise it is ignored.

      Compartment:

      Is applied as in case 1.a.

    5. Search with chaining

      Request:

      GET [base]/Patient?general-practitioner.identifier=123

      Type-Access:

      User must have read access to Patient, otherwise a 401 is returned. If the user has read access to Practitioner, the search argument is evaluated. Otherwise it is ignored as if the argument was not supported. If the chain has more than one link, read access is evaluated for every link in the chain.

      Compartment:

      Is applied as in case 1.a.

    6. Search with chaining into the compartment

      Request:

      GET [base]/Patient?link:Patient.identifier=456

      Type-Access:

      User must have read access to Patient, otherwise a 401 is returned.

      Compartment:

      Is applied to both Patient and link. In pseudo code: GET [base]/Patient?link:(Patient.identifier=456&Patient.identifier=123)&identifier=123 In this case there will probably be no results.

  2. Read: Is evaluated as a Search, but implicitly you only specify the _type and _id search parameters.

  3. VRead: If a user can Read the current version of the resource, he is allowed to get the requested version as well.

  4. Create

    1. Create on the compartment type

      Request:

      POST [base]/Patient

      Type-Access:

      User must have write access to Patient. Otherwise a 401 is returned.

      Compartment:

      A Search is performed as if the new Patient were in the database, like in case 1.a. If it matches the compartment filter, the create is allowed. Otherwise a 401 is returned.

    2. Create on a type related to compartment

      Request:

      POST [base]/Observation

      Type-Access:

      User must have write access to Observation. Otherwise a 401 is returned. User must also have read access to Patient, in order to evaluate the Compartment.

      Compartment:

      A Search is performed as if the new Observation were in the database, like in case 1.b. If it matches the compartment filter, the create is allowed. Otherwise a 401 is returned.

    3. Create on a type not related to compartment

      Request:

      POST [base]/Organization

      Type-Access:

      User must have write access to Organization. Otherwise a 401 is returned.

      Compartment:

      Is not evaluated.

  5. Update

    1. Update on the compartment type

      Request:

      PUT [base]/Patient/123

      Type-Access:

      User must have write access and read access to Patient, otherwise a 401 is returned.

      Compartment:

      User should be allowed to Read Patient/123 and Create the Patient provided in the body. Then Update is allowed.

    2. Update on a type related to compartment

      Request:

      PUT [base]/Observation/xyz

      Type-Access:

      User must have write access to Observation, and read access to both Observation and Patient (the latter to evaluate the compartment)

      Compartment:

      User should be allowed to Read Observation/123 and Create the Observation provided in the body. Then Update is allowed.

  6. Delete: Allowed if the user can Read the current version of the resource, and has write access to the type of resource.

  7. History: Allowed on the resources that the user is allowed to Read the current versions of (although it is theoretically possible that an older version would not match the compartment).

Note

A conditional create, update or delete (see the FHIR http specification), requires read permissions on the condition. Therefore, user/*.write will usually require additional read scopes.

Testing

Testing the access control functionality is possible on the publicly hosted test server as well as on a local instance.

On the public endpoint, there are 2 predefined users and 1 client, intended to be used from Postman:

  • Client:

    • Client ID: postman

    • Client secret: YXTHXspjK.2!rsz8jKQT

  • Auth URL: https://auth.fire.ly/connect/authorize

  • Access Token URL: https://auth.fire.ly/connect/token

  • User:

    • Username: alice

    • Password: password

You can test it locally using Firely Auth and Postman as a REST client. Please refer to Firely Auth Introduction for instructions:

You might also find it useful to enable more extensive authorization failure logging - Firely Server defaults to a secure setup and does not show what exactly went wrong during authorization. To do so, set the ASPNETCORE_ENVIRONMENT environment variable to Development.