Access Control API
We recently improved this API so it’s easier to program, and makes it harder to introduce security gaps in your system. If you were using it prior to September 6th 2021, read this guide for info on how to upgrade.
The access
property of the list configuration object configures who can query, create, update, and delete items in your Keystone system.
The access
property of the field configuration object configures who can read, create, and update specific field values of an item.
import { config, list } from '@keystone-next/keystone';import { text } from '@keystone-next/keystone/fields';export default config({lists: {ListKey: list({fields: {fieldName: text({ access: { /* ... */ }, }),},access: { /* ... */ },}),},});
List Access Control
Keystone lets you apply access control on a per-list basis. By default it allows all operations for all users. Access control is applied to the generated CRUD operations and mutations in the GraphQL API, and is applied before any hooks are executed.
List-level access control can be specified in three ways.
operation
access control lets you check the information in thecontext
andsession
objects to decide if the user is allowed to access the list.filter
access control lets you provide a GraphQL filter to restrict the items the user is allowed to access.item
access control lets you write a function which inspects the provided input data and the existing object (if it exists) and make a decision based on this extra data.
Access denied: Mutations return null
data with an access denied error. Queries never return access denied errors, and instead treat items as if they didn't exist.
If access is denied due to any of the access control methods, the following responses will be returned from the GraphQL API:
- Mutations
- Single operations return
null
and an access denied error. - Multi operations return a data array with
null
values for the items which have access denied. Error responses are returned for eachnull
item.
- Single operations return
- Queries
- Single item queries return
null
with no errors. - Many item queries filter out items which have access denied. No error responses are returned.
- Count queries will only count those items for which access is not denied. No error responses are returned.
- Single item queries return
Operation
Operation-level access control lets you control which operations can be accessed by a user based on the session
and context
.
Individual functions can be provided for each of the operations.
These functions must return true
if the operation is allowed, or false
if it is not allowed.
import { config, list } from '@keystone-next/keystone';export default config({lists: {ListKey: list({access: {operation: {query: ({ session, context, listKey, operation }) => true,create: ({ session, context, listKey, operation }) => true,update: ({ session, context, listKey, operation }) => true,delete: ({ session, context, listKey, operation }) => true,}},}),},});
The query
access control rule is applied when running GraphQL query operations.
It does not prevent a user reading the data returned by the mutation operations.
You should ensure that your create
, update
, and delete
are also configured to prevent access to protected data.
Filter
Filter-level access control lets you restrict which items can be operated on by providing a function which returns a GraphQL filter.
- For mutations, the access control filter will be combined with the unique identifier provided to the operation, and access will be denied if no item is found with this combined filter.
- For queries, the access control filter will be combined with the query filter and only items which match both filters will be returned.
In general, the filter access control functions will return GraphQL filters.
They can also return boolean values true
or false
to match or exclude all items.
import { config, list } from '@keystone-next/keystone';import { checkbox } from '@keystone-next/keystone/fields';export default config({lists: {ListKey: list({fields: {isReadable: checkbox(),isUpdatable: checkbox(),isDeletable: checkbox(),}access: {filter: {query: ({ session, context, listKey, operation }) => {return { isReadable: { equals: true } };},update: ({ session, context, listKey, operation }) => {return { isUpdatable: { equals: true } };},delete: ({ session, context, listKey, operation }) => {return { isDeletable: { equals: true } };},}},}),},});
Filter access control cannot be used on create
operations, as there is no pre-existing item to filter against.
To restrict create
operations configure either access.operation.create
or access.item.create
.
Filter based access control can impact the performance when querying related items on a list. Keystone optimises queries for to-one relationships, however these optimisations cannot be used in conjunction with access control filters.
Item (mutations only)
item
is the final and most powerful level of access control.
It’s available to create
, update
, and delete
mutations, and lets you write functions which have access to:
- the input data of the mutation (for
create
andupdate
operations), and - the existing item in the database (for
update
anddelete
operations).
These functions must return true
if the operation is allowed, or false
if it is not allowed.
import { config, list } from '@keystone-next/keystone';import { checkbox } from '@keystone-next/keystone/fields';export default config({lists: {ListKey: list({access: {item: {create: ({ session, context, listKey, operation, inputData }) => true,update: ({ session, context, listKey, operation, inputData, item }) => true,delete: ({ session, context, listKey, operation, item }) => true,}},}),},});
Item-level access control is not available for query
operations.
Applying access control after fetching items would lead to inconsistent pagination behaviour and incorrect count
results.
Function Arguments
List-level access control functions are passed a collection of arguments which can be used to determine whether the operation is allowed.
Argument | Description |
---|---|
session | The current session object. See the Sessions API for details. |
context | The KeystoneContext object of the originating GraphQL operation. |
listKey | The key of the list being operated on. |
operation | The operation being performed ('query' , 'create' , 'update' , 'delete' ). |
inputData | For create and update operations, this is the value of data passed into the mutation. (Item level only) |
item | The existing item being updated/deleted in update and delete operations. (Item level only) |
Field Access Control
Keystone also allows you to set up access control on a per-field basis.
Rules can be set for read
, create
and update
operations.
Each operation is defined by a function which returns true
if access is allowed and false
if access is not allowed.
Mutations
Field-level access control rules are applied after the list level access rules have been applied.
Access control rules are only applied to the fields that have an input value provided to the mutation.
If any of the provided fields fail their access control check, the whole operation is aborted.
The GraphQL API then returns null
along with an access denied error.
Read
Field-level access control rules are applied when trying to resolve a field on the output type.
If access is denied then the query will still return an item object, but the specific field will return null
.
No errors will be returned for read
access denied.
The read
access control is applied to fields returned from both queries and mutations.
import { config, list } from '@keystone-next/keystone';import { text } from '@keystone-next/keystone/fields';export default config({lists: {ListKey: list({fields: {fieldName: text({access: {read: ({ session, context, listKey, fieldKey, operation, item }) => true,create: ({ session, context, listKey, fieldKey, operation, inputData }) => true,update: ({ session, context, listKey, fieldKey, operation, inputData, item }) => true,},}),},}),},});
Field-level access control is not available for delete
operations. Restrict delete
operations at the List-level instead with access.operation.delete
, access.filter.delete
or access.item.delete
.
Function Arguments
Field-level access control functions are passed a collection of arguments which can be used to determine whether the operation is allowed.
Argument | Description |
---|---|
session | The current session object. See the Sessions API for details. |
context | The KeystoneContext object of the originating GraphQL operation. |
listKey | The key of the list being operated on. |
fieldKey | The key of the field being operated on. |
operation | The operation being performed ('read' , 'create' , 'update' ). |
inputData | For create and update operations, this is the value of data passed into the mutation. |
item | The existing item being read/updated in read and update operations. |