GraphQL authorization policy¶
The GraphQL authorization policy (graphql-authz) controls access to individual query and mutation fields of a GraphQL API. It checks the OAuth scopes and JWT claims of each request against rules that you configure per field.
A GraphQL API has a single endpoint, so the gateway can't attach a policy to individual operations at the route level. The graphql-authz policy inspects the request body instead, and applies your rules to each root field that the operation selects.
Before you begin¶
Attach an authentication policy, such as jwt-auth, before graphql-authz in the policy order. The authentication policy validates the token and makes its scopes and claims available to graphql-authz. For the list of available policies, see the Policy Hub.
How rules match fields¶
You configure rules in three places:
queries: rules for root fields of theQuerytype, by field name.mutations: rules for root fields of theMutationtype, by field name.global: one rule that applies to every query and mutation root field.
In queries and mutations, a rule named * matches every field of that type.
Every rule that matches a field applies, and all of them must grant access. A * rule or the global rule doesn't replace a field's own rule. If a field has its own rule and a * rule, the request must satisfy both.
If no rule matches a field, the policy doesn't govern that field. The request passes through for that field, and the rest of the API's policy chain decides whether to allow it.
Parameters¶
The policy takes the following parameters. You must set at least one of them.
| Parameter | Type | Description |
|---|---|---|
queries |
Array of rules | Rules for root Query fields. When present, it must contain at least one rule. |
mutations |
Array of rules | Rules for root Mutation fields. When present, it must contain at least one rule. |
global |
Object | A scopes or claims requirement that applies to every query and mutation root field, in addition to any matching rule. |
Each rule in queries or mutations takes the following fields. A rule must set at least one of scopes or claims, and the global rule takes the same fields except name.
| Field | Required | Description |
|---|---|---|
name |
Yes | The field name to authorize, such as books or addBook, or * to match every field of the type. |
scopes |
No | The scopes that the token must carry. Use allOf to require every listed scope, anyOf to require at least one, or both. |
claims |
No | The claims that the token must carry. Use allOf, anyOf, or both. Each entry has a claim name and a list of accepted values. |
If you set both allOf and anyOf, the request must satisfy both conditions. A claim matches when its value is one of the listed values.
How the policy evaluates a request¶
For each POST request, the policy runs these steps:
- Parse the GraphQL request body, and select the operation to run. If the document defines more than one operation, the request must set
operationName. - Collect the root fields that the operation selects, including fields inside fragments. The policy ignores
__typename. - Find every rule that matches each root field.
- If no rule matches any field, forward the request without further checks.
- If at least one field has a matching rule, require an authenticated request. If the request isn't authenticated, return
401. - Check every matching rule for every governed field. If any rule fails, return
403. - If every rule passes, forward the request unchanged.
A request can select more than one root field, for example query { books authors }. The request succeeds only if it satisfies the rules for every governed field.
Attach the policy¶
To attach the policy in the console, follow these steps:
- Open the GraphQL API, and then select Develop > Policies.
- Attach the
jwt-authpolicy, and configure your identity provider. - Find
graphql-authzin Available policies, and configure its rules. - Select Attach policy, and then select Save.
- Redeploy the API. For more information, see Deploy a GraphQL API.
For more information about the Policies page, see Apply policies to a GraphQL API.
To attach the policy by using the Platform API, add it to the policies array of the API metadata. For more information, see Manage GraphQL APIs with the Platform API.
Examples¶
The examples show the graphql-authz parameters for a bookstore API with books and authors queries, and addBook and deleteBook mutations.
Example 1: Basic field access control¶
The following configuration requires books:read for the books query and books:write for the addBook mutation:
queries:
- name: books
scopes:
anyOf:
- "books:read"
mutations:
- name: addBook
scopes:
anyOf:
- "books:write"
| Request | Caller | Result |
|---|---|---|
query { books { id } } |
Has books:read |
Authorized |
mutation { addBook(title: "Dune") { id } } |
Has books:read only |
403, because addBook requires books:write |
query { books { id } } |
Not authenticated | 401, because a rule governs books |
query { authors { id } } |
Any caller | Not governed by this policy, because no rule matches authors |
Example 2: Type-wide wildcard¶
The following configuration adds a * rule, so every query field also requires api:read:
| Request | Caller | Result |
|---|---|---|
query { authors { id } } |
Has api:read |
Authorized, because only the * rule matches authors |
query { books { id } } |
Has api:read only |
403, because the books rule also applies and requires books:read |
query { books { id } } |
Has books:read and api:read |
Authorized |
Example 3: Global baseline¶
The following configuration requires api:access for every query and mutation field, in addition to the field-level rules:
queries:
- name: books
scopes:
anyOf:
- "books:read"
mutations:
- name: addBook
scopes:
anyOf:
- "books:write"
global:
scopes:
anyOf:
- "api:access"
Fields without their own rule, such as authors, now require api:access instead of passing through.
| Request | Caller | Result |
|---|---|---|
query { authors { id } } |
Has api:access |
Authorized, because only the global rule matches authors |
query { books { id } } |
Has api:access only |
403, because the books rule requires books:read |
query { books { id } } |
Has books:read only |
403, because the global rule requires api:access |
query { books { id } } |
Has books:read and api:access |
Authorized |
Example 4: Claim-based rules¶
The following configuration restricts deleteBook to callers whose role claim is admin:
mutations:
- name: deleteBook
claims:
allOf:
- claim: role
values: ["admin"]
queries:
- name: books
scopes:
anyOf:
- "books:read"
- name: authors
scopes:
anyOf:
- "authors:read"
| Request | Caller | Result |
|---|---|---|
mutation { deleteBook(id: "1") } |
Claim role=admin |
Authorized |
mutation { deleteBook(id: "1") } |
Claim role=user |
403, because the claim value doesn't match |
query { books { id } authors { id } } |
Has books:read only |
403, because authors requires authors:read |
Example 5: Claims only¶
Rules don't need scopes. The following configuration uses only claims:
queries:
- name: books
claims:
anyOf:
- claim: department
values: ["sales", "marketing"]
mutations:
- name: deleteBook
claims:
allOf:
- claim: role
values: ["admin"]
global:
claims:
allOf:
- claim: tenant
values: ["acme-corp"]
| Request | Caller | Result |
|---|---|---|
query { books { id } } |
Claims department=sales and tenant=acme-corp |
Authorized |
query { books { id } } |
Claims department=engineering and tenant=acme-corp |
403, because the department value isn't accepted |
mutation { deleteBook(id: "1") } |
Claims role=admin and tenant=other-corp |
403, because the global rule requires tenant=acme-corp |
mutation { deleteBook(id: "1") } |
Claims role=admin and tenant=acme-corp |
Authorized |
Limitations¶
- Subscriptions: The policy doesn't govern
subscriptionoperations. The gateway doesn't route GraphQL subscriptions. - Fields without rules: The policy doesn't evaluate a field that no rule matches. To require a baseline scope or claim for every field, configure
global. - Adding
*orgloballater: These rules apply in addition to field-level rules. If you add one to an existing configuration, every field that it matches must also satisfy it.