Access Policies

Aidbox provides a flexible model to customise request authorization rules. User is allowed to declare a set of checks for all incoming requests. If the incoming request satisfies those checks, it's considered authorised and being processed further. Otherwise the request is denied and the client gets 403 Unauthorized. Such checks are declared with AccessPolicy resource.

AccessPolicy resource has the following structure:

resourceType: AccessPolicy
description: policy description text
# type of evaluation engine
engine: allow | sql | json-schema | complex
# JSON Schema for `json-schema` engine
schema: {}
# SQL string for `sql` engine
query: "SELECT true FROM ..."
# References to either Client, User or Operation resources
- { resourceType: Operation, id: op-id }
- { resourceType: User, id: user-1 }
- { resourceType: Client, id: client-1 }

It supports five evaluation modes (engines): SQL, JSON Schema, Allow, Matcho and Complex Engine. Evaluation engine specifies how checks are expressed: with a SQL statement, with a JSON Schema, or as a list of allowed endpoints.

Request Object Structure

Aidbox evaluates AccessPolicy against a request object, a data structure representing incoming HTTP request. It has the following structure:

# Request body (for PUT or POST)
body: null
# Parsed query-string params merged with URL params extracted by routing engine
params: {__debug: policy, type: Patient}
# Request method (get/post/put/delete)
request-method: get
# Request URI (no query string)
uri: /fhir/Patient
# Object containing current User resource (if any)
patient_id: 42
id: b66bc7c5-1a56-422f-8cf8-e64469135ce2
lastUpdated: '2018-10-31T13:43:18.566Z'
- code: updated
versionId: '6'
phone: 123-123-123
resourceType: User
# Parsed JWT claims (if any)
jwt: {sub: xxxxxxx, jti: xxxxxxx, iss: aidbox,
iat: 15409xxxxx, exp: 15409xxxxx}
# Query string
query-string: __debug=policy
# Client's IP address
# Client resource (if any)
id: b4930671-410c-462b-8b12-23cdef91af0c
lastUpdated: '2018-10-31T13:38:08.982Z'
- code: created
versionId: '4'
resourceType: Client
# Request scheme (http or https)
scheme: http
# Request headers
headers: {x-original-uri: '/fhir/Patient?__debug=policy', origin: '',
x-forwarded-host:, host:, user-agent: 'Mozilla/5.0
(Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100
Safari/537.36', content-type: 'text/yaml, application/json', x-forwarded-port: '443',
referer: '', connection: close, accept: text/yaml, accept-language: 'en-US,en;q=0.9,ru;q=0.8',
authorization: Bearer xxxxxx,
x-forwarded-for:, accept-encoding: 'gzip, deflate, br', x-forwarded-proto: https,
x-scheme: https, x-real-ip:}

Request Authorization Logic

AccessPolicy instance can be linked to User, Client or Operation resources with resource. If AccessPolicy has no links, it's considered as global policy. To authorize a request, Aidbox uses AccessPolicies linked to the current request's User, Client and Operation plus all global policies.

It iterates through them, evaluating each AccessPolicy against current request object. If some policy validates the request (evaluation result is true), the request considered authorized and Aidbox stops further policies evaluation. If all policies denied the request (all of them evaluated to false), then Aidbox denies such request and responds with 403 Unauthorized.

Performance consideration: Link you policy to User, Client or Operation to reduce number of checks per request!

If no AccessPolicy instances exist for a box, all requests will be denied.


Using __debug parameter

There is a special query-string parameter __debug=policy you can pass to every Aidbox request. It will toggle debug mode for Request Authorization Layer, and in this mode instead of actual response client will get an object containing:

  • full request object;

  • an array of existing AccessPolicies;

  • evaluation result for every AccessPolicy (under AccessPolicy.evalResult).

Using x-debug: policy header

For requests with the x-debug: policy header, details of access policy evaluation will be logged.

GET /Patient
x-debug: policy
# in aidbox logs
# :auth/trace-policy {:access-policy-id :policy-1, :policy-type "sql", ...
# :auth/trace-policy {:access-policy-id :policy-2, :policy-type "json-schema",...

Using the /auth/test-policy Operation

You can use a special operation POST /auth/test-policy to design policy without creating an AccessPolicy resource and for different users and clients. Post on the /auth/test-policy with a simulated request attribute (you can provide existing user-id and client-id — Aidbox will find and populate request) and temporal policy in the policy attribute. If you want to test JWT auth, put your token in the headers.authorization with the Bearer prefix — the token will be parsed and its claims appear in the request.jwt. JWT in a header is parsed but not validated. This allows you to test JWT policy without TokenIntrospector registration.

The response contains a result of evaluated policy.

POST /auth/test-policy
-- simulate request document
uri: '/Patient'
request-method: get
authorization: Bearer <your-jwt>
data: {role: 'admin'}
-- or
-- user-id: 'user-1' -- aidbox will find user in database
id: basic
grant_types: ['basic']
-- or
-- client-id: 'client-1' -- aidbox will find client in database
engine: sql
query: 'SELECT {{user.role}} FROM {{!params.resource/type}}'
-- response
uri: /Patient
request-method: get
user: {role: admin}
params: {resource/type: Patient}
#jwt: ...jwt-claims
- get
- {name: resource/type}
action: proto.operations/search
module: proto
id: Search
-- original policy
engine: sql
sql: {query: 'SELECT {{user.role}} FROM {{!params.resource/type}}'}
-- result of policy evaluation
eval-result: false
query: ['SELECT ? FROM "patient"', admin]

JSON Schema Engine

JSON Schema engine allows to put JSON Schema under the AccessPolicy.schema element and this schema will be used to validate the request object. Currently supported JSON Schema version is draft-07.


The following policy requires presence of the request.user attribute (only authenticated requests are allowed):

resourceType: AccessPolicy
description: Allow
engine: json-schema
type: object
required: ["user"]

SQL Engine

SQL Engine executes SQL statement and uses its result as an evaluation result. The SQL statement should return a single row with just one column, i.e.:

SELECT true FROM patient WHERE id = {{jwt.patient_id}} LIMIT 1;

SQL statement can include interpolations in double curly braces, like in the example above. String inside curly braces will be used as a path to get value from the request object.


Assuming that User has a reference to Practitioner resource through element, the following policy allows requests only to /fhir/Patient/<patient_id> URLs, and only for those patients who have the Patient.generalPractitioner element referencing same practitioner as the current User. In other words, User as a Practitioner is only allowed to see patients who are referencing him with the Patient.generalPractitioner.

query: |
{{user}} IS NOT NULL
AND {{uri}} LIKE '/fhir/Patient/%'
AND resource->'generalPractitioner' @>
'Practitioner', 'id', {{}}::text))
FROM patient WHERE id = {{params.resource/id}};
engine: sql
id: practitioner-only-allowed-to-see-his-patients
resourceType: AccessPolicy

Interpolation Rules

In your SQL query, you can parameterize with attributes from the request object using {{path}} syntax. For example, to get a role from user {data: {role: 'admin'}} you can write {{}}. Parameter expressions are escaped by default to protect from SQL injection. If you want to make dynamic queries (parameterize table name, for example), you have to use {{!path}} syntax. For example, the expression SELECT true from {{!params.resource/type}} limit 1 with params = {resource/type: "Patient"} will be transformed into SELECT true from "patient". Such identifier names are double quoted and lower-cased by default.

Allow Engine

Allow Engine constantly evaluates to true regardless the content of the request object.

Complex Engine

Complex engine provides ability to include several checks into a single policy and apply "AND" / "OR" operator on results. It's allowed to use any policy engine to define a check, you can even use "complex" engine to get sub-expression:

- { engine: "sql", sql: { query: "select true" } } # Check 1
- engine: complex
- { engine: "sql", sql: { query: "select false" } } # Check 2
- { engine: "sql", sql: { query: "select false" } } # Check 3
engine: complex
id: complex-example-1
resourceType: AccessPolicy

Policy in the example above represents the following logical expression: check 1 AND (check 2 OR check 3). It's forbidden to have both and and or keys on the same level.


Let's split SQL Policy example into two separate checks and combine them with the AND operator:

# Check: request.user && are required
- engine: json-schema
type: object
required: ["user"]
type: object
required: ["data"]
type: object
required: ["practitioner_id"]
# Check: Current practitioner is patient's generalPractitioner
- engine: sql
query: |
{{uri}} LIKE '/fhir/Patient/%'
AND resource->'generalPractitioner' @>
'Practitioner', 'id', {{}}::text))
FROM patient WHERE id = {{params.resource/id}};
engine: complex
id: complex-example-2
resourceType: AccessPolicy

Matcho Engine

This custom DSL engine has limited expressiveness, but is very compact and declarative. The general idea is pattern matching with few extensions.

resourceType: AccessPolicy
engine: matcho
# user.role should be equal to admin
role: admin
# should be present
data: {practitioner_id: present?}
# uri match regexp /Encounter/.*
uri: '#/Encounter.*'
# request method should be one of get or post
request-method: {$enum: ['get', 'post']}
# parameter practitioner should be equal to

Match DSL definition:

  • If pattern (match) is object, search for inclusion of this object into subject. For example: {x: 1} matches {x: 1, y: 2 ....}. This algorithm is recursive — {a: {b: 5}} matches {a: {b: 5, ...}...}

  • Objects with special keys:

    • $enum — test subject is equal to one of items in the enumeration. {request-method: {$enum: ['get','post']}} matches {request-method: 'post'}

    • $contains if a subject is a collection, then search at least one match. {type: {$contains: {system: 'loinc'}} matches {type: [{system: 'snomed'}, {system: 'loinc'}]}

    • $one-of — try to match one of patterns. {a: {$one-of: [{b: present?}, {c: present?}]} matches {a: {c: 5}}

    • $reference - parse Reference or string into aidbox format. Examples:

      • Parse Reference elements

        • parser: {reference: "Patient/pid"} => {id: "pid", resourceType: "Patient"}

        • {resource: {patient: {$reference: {id: ''}}}

      • Parse reference string

        • "Patient/pid" => {id: "pid", resourceType: "Patient"}

        • {params: {subject: {$reference: {id: ''}}}

  • For array, match the first item in the pattern with the first item in the subject. [1,2] matches [1,2,3...]

  • Primitive values (strings, numbers and booleans) are compared by value

  • If a string starts with '#' , it will be transformed into regex and matched as regex. {a: '#\\d+'} matches {a: '2345'}

  • If a string starts with '.' , it's interpreted as a pointer to another path in the subject to compare. For example: {params: {user_id: ''}} matches {user: {id: 1}, params: {user_id: 1}}, i.e. == param.user_id

  • There are several special string literals postfixed with the ?

    • present? — matches the subject if it is not null, i.e. {a: 'present?'} matches {a: 5} or {a: {b: 6}}

    • nil? — matches if nil/null — {a: nil?} matches {b: 6}

    • not-blank? — matches not blank string.

Need more rules? Contact us on the telegram chat!

Here are some examples:

# only users with role admin
user: {role: {$contains: 'admin'}}
# only authorized users get patients or encountres
user: present?
request-method: get
'resource/type': {$enum: ['Patient', 'Encounter'}
# search encounter with required parameter practitioner,
# wich should be equal to
user: {data: {pract_id: present?}}
uri: '/Encounter'
practitioner: ''