--- description: Achieve logical multi-tenancy with Aidbox --- # Organization-based hierarchical access control Hierarchical organization-based access control in Aidbox allows for the restriction of access to data based on the organization to which it belongs. When this feature is enabled, the FHIR Organization resource in Aidbox gains new semantics and functionality. This means that when users interact with the Organizational FHIR API, they are only able to access the resources that belong to their organization or tenant. The hierarchical organization-based access control ensures that data is logically isolated and accessible only within the appropriate organizational context. ## Problem FHIR resources must be separated per organizations. Organizations can be nested. Every organization has access to their own resources and to the nested organization resources. ## Solution Let's consider the next organization structure. There are two independent organizations Org A & Org D, each of them has nested, dependent organizations. Org B & Org C are nested to Org A, and Org E is nested to Org D.
Organization hierarchy diagram showing nested organizations: Org A with child Org B and Org C, Org D with child Org E

Organization hierarchy structure

To achieve such a behavior, you may consider an Aidbox feature called organization-based access control. Let's create the organization structure in Aidbox: ``` POST /fhir resourceType: Bundle type: batch entry: - request: method: PUT url: Organization/org-a resource: name: Organization A resourceType: Organization - request: method: PUT url: Organization/org-b resource: name: Organization B resourceType: Organization partOf: reference: Organization/org-a - request: method: PUT url: Organization/org-c resource: name: Organization C resourceType: Organization partOf: reference: Organization/org-a - request: method: PUT url: Organization/org-d resource: name: Organization D resourceType: Organization - request: method: PUT url: Organization/org-E resource: name: Organization E resourceType: Organization partOf: reference: Organization/org-d ``` When an Organization resource is created, a dedicated FHIR API is deployed for that organization. This API provides access to the associated FHIR resources. Nested organization FHIR resources are accessible through the parent Organization API. The Organization-based FHIR API base url: ``` /Organization//fhir ``` The Organization-based [Aidbox API](../../../api/rest-api/other/aidbox-and-fhir-formats.md) base url: ``` /Organization//aidbox ```
Diagram showing FHIR API endpoints for each organization in the hierarchy

FHIR APIs reflection in organization-based access control

### Try Org-BAC Let's play with new APIs. We will create a Patient resource in Org B: ``` PUT /Organization/org-b/fhir/Patient/pt-1 content-type: text/yaml accept: text/yaml name: [{given: [John], family: Smith}] gender: male ``` Now we can read it: ``` GET /Organization/org-b/fhir/Patient/pt-1 ``` Note, that patient has a `https://aidbox.app/tenant-organization-id` extension, which references `org-b`. ``` id: >- pt-1 meta: extension: - url: https://aidbox.app/tenant-organization-id valueReference: reference: Organization/org-b - url: ex:createdAt valueInstant: '2024-10-03T15:02:09.039005Z' lastUpdated: '2024-10-03T15:02:09.039005Z' versionId: '336' name: - given: - John family: Smith gender: male resourceType: Patient ``` The resource is also accessible through Org A API: ``` GET /Organization/org-a/fhir/Patient/pt-1 ``` But this resource is not accessible through Org C, Org D and Org E API: ``` GET /Organization/org-c/fhir/Patient/pt-1 # 403 Forbidden ``` ### Limitations Some Aidbox features do not respect Organization-based access control. The resources managing these features are inaccessible under the Organization API. For example, there is `SubsSubscription` resource. Any request to the `SubsSubscription` resource will return `OperationOutcome` with the `422` HTTP code and issue code `not-supported`. If `SubsSubscription` resource is created using regular API (not Organization API), Aidbox Subscriptions will send notifications irrespectively of Organization hierarchy. ### Topic-Based Subscriptions with Organization Hierarchy {% hint style="warning" %} Organization-based hierarchical filtering is available starting from version 2509. {% endhint %} `AidboxSubscriptionTopic` and `AidboxTopicDestination` support organization-based hierarchical filtering. For more details, see [Aidbox Topic-Based Subscriptions](../../../modules/topic-based-subscriptions/aidbox-topic-based-subscriptions.md#organization-based-hierarchical-filtering). ## FHIR API over Organization resources ### Create ``` POST /Organization//fhir/ ``` #### Conditional Create {% hint style="warning" %} Conditional operations are available starting from version 2509. {% endhint %} Create the resource only if no existing resource matches the given search criteria. ``` POST /Organization//fhir/Observation If-None-Exist: identifier=http://acme.org/obs|12345 Content-Type: application/fhir+json { "resourceType": "Observation", "status": "final", "identifier": [{ "system": "http://acme.org/obs", "value": "12345" }], "code": { "text": "Example observation" } } ``` ### Read ``` GET /Organization//fhir// ``` ### Update #### Put ``` PUT /Organization//fhir// ``` #### Conditional Put {% hint style="warning" %} Conditional operations are available starting from version 2509. {% endhint %} Update a resource that matches the search; create if none exists. ``` PUT /Organization//fhir/Observation?identifier=http://acme.org/obs|12345 Content-Type: application/fhir+json { "resourceType": "Observation", "status": "final", "identifier": [{ "system": "http://acme.org/obs", "value": "12345" }], "code": { "text": "Example observation (updated)" } } ``` #### Patch
PATCH <AIDBOX_BASE_URL>/Organization/<org-id>/fhir/<resource-type>/<id>?[_method={ json-patch | merge-patch | fhirpath-patch }]
All PATCH methods are supported under org-scoped API. See also [patch](../../../api/rest-api/crud/patch.md) #### Conditional Patch {% hint style="warning" %} Conditional operations are available starting from version 2509. {% endhint %} Patch a resource matched by a search expression. ``` PATCH /Organization//fhir/Observation?identifier=http://acme.org/obs|12345&_method=merge-patch Content-Type: application/merge-patch+json { "status": "final" } ``` ### Delete ``` DELETE /Organization//fhir// ``` #### Conditional Delete {% hint style="warning" %} Conditional operations are available starting from version 2509. {% endhint %} Delete resource(s) matching a search expression at the type endpoint under the organization. ``` DELETE /Organization//fhir/Observation?identifier=http://acme.org/obs|12345 ``` ### Search ``` GET /Organization//fhir/ ``` {% hint style="warning" %} The search API does not support search parameters: * `_assoc` * `_with` {% endhint %} Since 2505, [\_has search parameter](../../../api/rest-api/fhir-search/chaining.md) is supported. ### $everything ``` GET /Organization//fhir/Patient/$everything ``` See also [$everything on Patient](../../../api/rest-api/everything-on-patient.md) ### $document ``` GET /Organization//fhir/Composition/$document ``` See also [$document endpoint](../../../api/rest-api/other/document.md) ### History Resource full history ``` GET /Organization//fhir///_history ``` Specific version history entry ``` GET /Organization//fhir///_history/ ``` ### Bundle Supported `transaction` and `batch` bundle types. ```yaml POST /Organization/org-a/fhir/ Accept: text/yaml Content-Type: text/yaml resourceType: Bundle # transaction | batch type: transaction entry: - request: method: POST url: 'Patient' resource: birthDate: '2021-01-01' id: 'pt-1' meta: organization: id: 'org-c' resourceType: 'Organization' - request: method: POST url: 'Patient' resource: birthDate: '2021-01-01' id: 'pt-2' - request: method: PATCH url: 'Patient/pt-3?_method=json-patch' resource: - op: replace path: birthDate value: '2021-01-01' ``` It is also possible to use org-based url in a `request.url`:
POST /
Accept: text/yaml
Content-Type: text/yaml

resourceType: Bundle
# transaction | batch
type: transaction
entry:
- request:
    method: GET
    url: '/Organization/org-a/fhir/Patient/pt-1'
- request:
    method: PUT
    url: '/Organization/org-b/fhir/Patient/pt-3'
  resource:
    birthDate: '2021-01-01'
- request:
    method: POST
    url: '/Organization/org-a/fhir/Patient'
  resource:
    birthDate: '2021-01-01'
    id: 'pt-4'
See also [Transactions page](../../../api/batch-transaction.md) #### Conditional Create with Bundle {% hint style="warning" %} Conditional operations are available starting from version 2509. {% endhint %} ``` POST /Organization/org-a/fhir/ Content-Type: application/fhir+json { "resourceType": "Bundle", "type": "batch", "entry": [ { "request": { "method": "POST", "url": "Observation", "ifNoneExist": "identifier=http://acme.org/obs|12345" }, "resource": { "resourceType": "Observation", "status": "final", "identifier": [{ "system": "http://acme.org/obs", "value": "12345" }], "code": { "text": "Example observation" } } } ] } ``` #### Conditional Update with Bundle {% hint style="warning" %} Conditional operations are available starting from version 2509. {% endhint %} ``` POST /Organization/org-a/fhir/ Content-Type: application/fhir+json { "resourceType": "Bundle", "type": "batch", "entry": [ { "request": { "method": "PUT", "url": "Observation?identifier=http://acme.org/obs|12345" }, "resource": { "resourceType": "Observation", "status": "final", "identifier": [{ "system": "http://acme.org/obs", "value": "12345" }], "code": { "text": "Example observation" } } } ] } ``` #### Conditional Delete with Bundle {% hint style="warning" %} Conditional operations are available starting from version 2509. {% endhint %} ``` POST /Organization/org-a/fhir/ Content-Type: application/fhir+json { "resourceType": "Bundle", "type": "batch", "entry": [ { "request": { "method": "DELETE", "url": "Observation?identifier=http://acme.org/obs|12345" } } ] } ``` ### Metadata ``` GET /Organization//fhir/metadata ``` ### AidboxQuery {% hint style="info" %} [Learn more about AidboxQuery](../../../api/rest-api/aidbox-search.md#aidboxquery). {% endhint %} To use `$query` endpoint under organization-based hierarchical access control, it is necessary to create explicitly `organization` param in `AidboxQuery`. ```yaml PUT /AidboxQuery/ params: organization: type: string query: "SELECT * from patient pt WHERE pt.resource#>>'{meta,organization,id}' = {{params.organization}}" count-query: "SELECT count(*) from patient pt WHERE pt.resource#>>'{meta,organization,id}' = {{params.organization}}" type: query ``` Now `org-id` is automatically available in the query in `{{params.organization}}`. ```yaml GET /Organization//$query/ ``` ### GraphQL ``` POST /Organization//aidbox/$graphql ``` Since version 2503 GraphQL is supported in OrgBAC mode. Note that it can be accessed only on the non-FHIR endpoint, because our GraphQL implementation is slightly different from FHIR. See also: [graphql-api.md](../../../api/graphql-api.md) ### Group-level Export #### Start Export ``` GET /Organization//fhir/Group//$export ``` Starts a group-level export for the specified organization and group. #### Check Export Status ``` GET /Organization//fhir/$export-status/ ``` Checks the status of an export job for the specified organization. #### Cancel Export ``` DELETE /Organization//fhir/$export-status/ ``` Cancels an active export job for the specified organization. See also [$export](../../../api/bulk-api/export.md#group-level-export) ## Authentication ### Login View ``` GET /Organization//auth/login ``` Returns the login view for the specified organization. ### Login ``` POST /Organization//auth/login ``` Performs loginfor the specified organization. ## Shared resource mode By default, nested API has no access to a resource that belongs to the upper organizations. Sometimes it is necessary to have resources that can be accessed by the nested APIs. To achieve it the resource should be marked as `shared`. {% hint style="warning" %} Update and delete operations are not allowed from nested organizations' APIs. To update or delete `shared`resource use its root organization API. {% endhint %} ### Create a shared resource To create a shared resource, use the `https://aidbox.app/tenant-resource-mode` extension. ``` PUT /Organization/org-a/fhir/Practitioner/prac-1 content-type: text/yaml meta: extension: - url: https://aidbox.app/tenant-resource-mode valueString: "shared" ``` ### Access shared resource from a nested API Now, if `org-b` is a child organization of `org-a`, (**Organization.partOf** references `org-a`), we get the access to the shared resource: ``` GET /Organization/org-b/fhir/Practitioner/prac-1 ``` ## See also {% content-ref url="../../../tutorials/security-access-control-tutorials/how-to-enable-hierarchical-access-control.md" %} [how-to-enable-hierarchical-access-control.md](../../../tutorials/security-access-control-tutorials/how-to-enable-hierarchical-access-control.md) {% endcontent-ref %}