Access control
Control who can do what with your GraphQL API.
Note: This is the API documentation for access control. For getting started, see the Access control guide or the Authentication Guide.
There are three domains of access control:
- List level
- Field level
- Custom schema
To set defaults for all lists, fields, and custom schema, use the defaultAccess config when
creating a Keystone instance. Each defaults to true if omitted.
const keystone = new Keystone('My App', {
defaultAccess: {
list: true,
field: true,
custom: true,
},
});
The auth operation
In addition to the standard Create/Read/Update/Delete (CRUD) operations, Keystone includes an Authenticate (auth) operation.
Access to this operation may be configured at list level (not field level) and controls whether authentication queries and mutations are accessible on that list.
If you have a List which is being used as the target of an Authentication Strategy, you should set access: { auth: true } on that list.
List level access control
List level access control can have varying degrees of specificity depending on how much control you need.
A key on the list config, access can be specified either as a single control,
covering all CRUDA operations, or as an object keyed by CRUDA operation names.
There are 3 ways to define the values of access, in order of flexibility:
- Static
- Imperative
- Declarative
interface GraphQLWhere {
[key: string]: any;
}
interface AccessInput {
authentication: {
item?: {};
listKey?: string;
};
listKey?: string;
operation?: string;
originalInput?: {};
gqlName?: string;
itemId?: string;
itemIds?: [string];
}
type StaticAccess = boolean;
type ImperativeAccess = (arg: AccessInput) => boolean;
type DeclarativeAccess = GraphQLWhere | ((arg: AccessInput) => GraphQLWhere);
interface GranularAccess {
create?: StaticAccess | ImperativeAccess;
read?: StaticAccess | ImperativeAccess | DeclarativeAccess;
update?: StaticAccess | ImperativeAccess | DeclarativeAccess;
delete?: StaticAccess | ImperativeAccess | DeclarativeAccess;
auth?: StaticAccess;
}
type ListConfig = {
access: StaticAccess | ImperativeAccess | GranularAccess;
};
GraphQLWhere matches the where clause on the GraphQl type. For instance, on
the list User it would match the input type UserWhereInput.
AccessInput has the following properties:
| Property | Description |
|---|---|
authentication | The currently authenticated user. |
authentication.item | The details of the current user. Will be undefined for anonymous users. |
authentication.listKey | The list key of the currently authenticated user. Will be undefined for anonymous users. |
listKey | The key of the list being operated on. |
operation | The CRUDA operation being performed ('create', 'read', 'update', 'delete', 'auth'). |
originalInput | For create and update mutations, this is the data as passed in the mutation. |
gqlName | The name of the query or mutation which triggered the access check. |
itemId | The id of the item being updated/deleted in singular update and delete operations. |
itemIds | The ids of the items being updated/deleted in multiple update and delete operations. |
context | The context of the originating GraphQL operation. |
When resolving StaticAccess:
true: Allow accessfalse: Do not allow access
Definition of access operations:
| Operation | Description |
|---|---|
create | Ability to create new items in the list. |
read | Ability to view / fetch data on any items in the list. |
update | Ability to alter data on any items in the list. |
delete | Ability to remove an item from the list. |
auth | Ability to use this list for authentication. |
When access is denied, the GraphQL response will contain an error with
type: 'AccessDeniedError', and null for the data.
Note: The
createoperation cannot be givenDeclarativeAccess- it does not make sense to do so and will throw an error if attempted. Additionally, theauthoperation control must be of typeStaticAccess.
Shorthand static Boolean
Great for blanket access control for lists you want everyone/no one to see.
keystone.createList('User', {
access: true,
});
Note: When set to
false, the list queries/mutations/types will not be included in the GraphQL schema.
Granular static Boolean
Use when you need some more fine grained control over what actions users can perform.
keystone.createList('User', {
access: {
create: true,
read: true,
update: true,
delete: true,
auth: true,
},
});
Note: When set to
false, the list queries/mutations/types exclusive to that operation will not be included in the GraphQL schema. For example, settingcreate: falsewill cause thecreateXXXXmutation to be excluded from the schema,update: falsewill cause theupdateXXXXmutation to be excluded, and so on.
Shorthand imperative Boolean
Enables turning access on/off based on the currently authenticated user.
keystone.createList('User', {
access: ({ authentication: { item, listKey } }) => {
return true;
},
});
Note: Even when returning
false, the queries/mutations/types will be included in the GraphQL Schema.
Granular imperative Boolean
Use when you need some more fine grained control over what actions some or all anonymous/authenticated users can perform.
keystone.createList('User', {
access: {
create: ({ authentication: { item, listKey } }) => true,
read: ({ authentication: { item, listKey } }) => true,
update: ({ authentication: { item, listKey } }) => true,
delete: ({ authentication: { item, listKey } }) => true,
},
});
Note: Even when returning
false, the queries/mutations/types for that operation will be included in the GraphQL Schema. For example,create: () => falsewill still include thecreateXXXXmutation in the GraphQL Schema, and so on.
GraphQLWhere
In the examples below, the name_contains: 'k' syntax matches the UserWhereInput GraphQL type for the list.
- For singular
read/update/deleteoperations, when theGraphQLWhereclause results in 0 items, anAccessDeniedErroris returned. - For batch
readoperations (eg;query { allUsers }), when theGraphQLWhereclause results in 0 items returned, no error is returned. - For
createoperations, anAccessDeniedErroris returned if the operation is set to / returnsfalse
Granular static GraphQLWhere
Use when you need some more fine grained control over what items a user can perform actions on.
keystone.createList('User', {
access: {
create: true,
read: { name_contains: 'k' },
update: { name_contains: 'k' },
delete: { name_contains: 'k' },
},
fields: {
name: { type: Text },
},
});
Granular imperative GraphQLWhere
Use when you need some more fine grained control over which items and actions anonymous/authenticated users can perform.
keystone.createList('User', {
access: {
create: ({ authentication: { item, listKey } }) => true,
read: ({ authentication: { item, listKey } }) => ({
state_not: 'deactivated',
}),
update: ({ authentication: { item, listKey } }) => ({
state_not: 'deactivated',
}),
delete: ({ authentication: { item, listKey } }) => ({
state_not: 'deactivated',
}),
},
fields: {
state: {
type: Select,
options: ['active', 'deactivated'],
defaultValue: 'active',
},
},
});
Field level access control
A key on the field config, access can be specified either as a single control,
covering all CRU operations, or as an object keyed by CRU operation names.
Important: Unlike List level access, it is not possible to specify a Declarative where clause for Field level access.
There are 2 ways to define the values of access, in order of flexibility:
- Static
- Imperative
interface AccessInput {
authentication: {
item?: {};
listKey?: string;
};
listKey?: string;
fieldKey?: string;
originalInput?: {};
existingItem?: {};
operation?: string;
gqlName?: string;
itemId?: string;
itemIds?: [string];
context?: {};
}
type StaticAccess = boolean;
type ImperativeAccess = (arg: AccessInput) => boolean;
interface GranularAccess {
create?: StaticAccess | ImperativeAccess;
read?: StaticAccess | ImperativeAccess;
update?: StaticAccess | ImperativeAccess;
}
type FieldConfig = {
access: StaticAccess | ImperativeAccess | GranularAccess;
};
Note: Fields do not have
deleteorauthaccess controls - these controls exists on the list level only (it's not possible to "delete" an existing field value - only to modify it, and authentication is list-wide).
| Property | Description |
|---|---|
authentication | The currently authenticated user. |
authentication.item | The details of the current user. Will be undefined for anonymous users. |
authentication.listKey | The list key of the currently authenticated user. Will be undefined for anonymous users. |
listKey | The key of the list being operated on. |
fieldKey | The key of the field being operated on. |
originalInput | The data as passed in the mutation for create and update mutations (undefined for read). |
existingItem | The existing item this field belongs to for update mutations and read queries (undefined for create). |
operation | The CRU operation being performed ('create', 'read', 'update'). |
gqlName | The name of the query or mutation which triggered the access check. |
itemId | The id of the item being updated/deleted in singular update and delete operations. |
itemIds | The ids of the items being updated/deleted in multiple update and delete operations. |
context | The context of the originating GraphQL operation. |
When defining StaticAccess:
true: Allow accessfalse: Do not allow access
Definition of access operations:
| Operation | Description |
|---|---|
create | Ability to set the value of the field when creating a new item. |
read | Ability to view / fetch the value of this field on an item. |
update | Ability to alter the value of this field on an item. |
When access is denied, the GraphQL response will contain an error with type: 'AccessDeniedError',
and null for the field.
Let's break it down into concrete examples:
Shorthand static Boolean
Great for blanket access control for fields you want everyone/no one to see.
keystone.createList('User', {
fields: {
name: {
type: Text,
access: true,
},
},
});
Note: When set to
false, the list queries/mutations/types will not include this field in the GraphQL schema.
Granular static Boolean
Use when you need some more fine grained control over what actions users can perform with this field.
keystone.createList('User', {
fields: {
name: {
type: Text,
access: {
create: true,
read: true,
update: true,
},
},
},
});
Note: When set to
false, this field will not be included in GraphQL queries/mutations/types exclusively used by that operation. Eg, settingupdate: falsein the example above will remove thenamefield from theUserUpdateInputtype but may still include the field inUserCreateInputfor example.
Shorthand imperative Boolean
Enables turning access on/off based on the currently authenticated user.
keystone.createList('User', {
fields: {
name: {
type: Text,
access: ({ authentication: { item, listKey }, existingItem }) => {
return true;
},
},
},
});
Note: Even when returning
false, the queries/mutations/types will include the field in the GraphQL Schema.
Granular imperative Boolean
Use when you need some more fine grained control over what actions some or all anonymous/authenticated users can perform.
keystone.createList('User', {
access: {
create: ({ authentication: { item, listKey }, existingItem }) => true,
read: ({ authentication: { item, listKey }, existingItem }) => true,
update: ({ authentication: { item, listKey }, existingItem }) => true,
},
});
Note: Even when returning
false, this field will be included in GraphQL queries/mutations/types exclusively used by that operation. Eg, settingupdate: () => falsein the example above will still include thenamefield in theUserUpdateInputtype.
Custom schema access control
Custom GraphQL schema can also be access-controlled.
Each custom type, query, and mutation accepts an access key.
There are two ways to define the value of access:
- Static
- Imperative
interface AccessInput {
item {};
args {} ;
context: {};
info: {};
authentication: {
item?: {};
listKey?: string;
};
gqlName: string;
}
type StaticAccess = boolean;
type ImperativeAccess = (arg: AccessInput) => boolean;
type CustomOperationConfig = {
access: StaticAccess | ImperativeAccess;
};
Static boolean
keystone.extendGraphQLSchema({
queries: [
{
schema: 'getUserByName(name: String!): Boolean',
resolver: async (item, args, context, info, { query, access }) => {...},
access: true,
},
],
});
Useful if default custom access controls are set to
false.
NOTE: When set to false, the custom queries/mutations/types will not be included in the GraphQL schema.
Imperative boolean
keystone.extendGraphQLSchema({
queries: [
{
schema: 'getUserByName(name: String!): Boolean',
resolver: async (item, args, context, info, { query, access }) => {...},
access: async ({ item, args, context, info, authentication: { item: authedItem, listKey }, gqlName }) => {
return true;
},
},
],
});
Enables turning access on/off based on the currently authenticated user.
NOTE: Even when returning false, the custom queries/mutations/types will be included in the GraphQL Schema.
On this page
- The auth operation
- List level access control
- Shorthand static Boolean
- Granular static Boolean
- Shorthand imperative Boolean
- Granular imperative Boolean
- GraphQLWhere
- Field level access control
- Shorthand static Boolean
- Granular static Boolean
- Shorthand imperative Boolean
- Granular imperative Boolean
- Custom schema access control
- Static boolean
- Imperative boolean