A Policy
is a JSON object describing a single authorization case. Multiple policies are used together to form a functional authorization engine.
More information on how to use Policies, and some example policies to get you started.
Policies are added at runtime and at this stage cannot be altered without restarting the server. The expectation is that policies would be stored in version control next to your server, and when you update the policies, you will redeploy your server.
Policies with explicit denies (any policy that has
deny
as theeffect
) superseed any other policy. Therefore a policy withdeny
is a hard stop for access, no other policy can ever grant access to that role.
Policy
Every policy needs a unique ID. This is intended for tracing back the results of an authorization event to the policy that decided the outcome.
Property | Type | Description |
---|---|---|
id | string | A unique string describing this policy. |
effect | PolicyEffect or string | One of PolicyEffect.Allow or PolicyEffect.Deny or just a string of Allow or Deny. |
denyType? | string | Returned to the client with PolicyEffect.Deny, for example mfa-required. |
actions | Array of string | One or more of query, mutation, subscription. |
resources | Array of string | See resources |
roles | Array of string | An array of roles, see roles |
conditions | Array of PolicyCondition | See conditions |
Resources
A resource is a string representation of the current resolver from the root of the GraphQL schema.
The top level type is seperated by two colons ::
and all subsequent types are seperated by one
colon :
.
If you take the following schema:
type User { |
And a query of:
query topPosts { |
Then the following resources would be checked for policies:
Query::topPosts |
For these resources to be accessible you would need to specify all fields in your resource array for a policy. Or, you can use a wildcard.
Wildcards
Instead of entering every field for your resources, you can wildcard the entire type. This saves you needing to update your policies when you add a new field.
So for above, we could intead use the following resources: Query::*
, Post::*
, Author::*
.
Itโs reccomneded you keep in mind explicit deny polices when using wildcards. You can have two policies one that grants access to an entire type, and another that explicity denys a specific field, to allow a role access to all but a specific field on that type.
Roles
A role is a string defined on context.user.roles
which is populated by your authentication function. The default role for a user is anonymous
.
You can also use wildcards with roles, but they need to be specified as a role in the array. For example roles: ["*"]
is a wildcard for all roles. And roles: ["admin-*"]
is a wildcard for all roles starting with admin-
.
Conditions
Conditions let you fine tune your policy. For example you may only apply a policy to users of a certain IP range, or a time since their session started.
Condition
Property | Type | Description |
---|---|---|
field | string | A dot path to the key of the context object to compare, eg context.request.ip. |
expected (optional) |
Array of number or Array of string |
An array of expected values |
expectedOnContext (optional) |
Array of string | A dot path to the context object |
operator | PolicyOperator | One of match, notMatch, lessThan, greaterThan |
You must pass either
expected
orexpectedOnContext
if you are adding a condition to a policy.
The context
object
Both field
and expectedOnContext
refer to a context
object that is passed by Bunjil on every authorization check.
The context object is constructed as follows:
const context: any = { |
You can use expectedOnContext
to compare against anything else on the context
object.
Evaluation Process
If there are multiple conditions
they are evaluated with a boolean AND
. Which is to say all conditions must be true
for the policy to evaluate to true
.
If there are multiple expectedValues
or expectedValuesOnContext
on a condition, they are evaluated with a boolean OR
. Which is to say, if any of the values match, then the condition outcome is true.
Expected Values
expected
If you are passing an array of expected
values, it must be an array of strings
, or an array of numbers
. It cannot be a mix.
If you pass numbers
then operator
must be either a numeric operator PolicyOperator.lessThan
PolicyOperator.greaterThan
. If your value is a number passed as a string, itโs coerced into a number
.
If you pass string
then operator
must be either a string operator PolicyOperator.match
PolicyOperator.notMatch
.
expectedOnContext
This must be a dot path to another value on the context object to compare the field
with.