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,
},
});
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 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
create
operation cannot be givenDeclarativeAccess
- it does not make sense to do so and will throw an error if attempted. Additionally, theauth
operation control must be of typeStaticAccess
.
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.
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: false
will cause thecreateXXXX
mutation to be excluded from the schema,update: false
will cause theupdateXXXX
mutation to be excluded, and so on.
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.
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: () => false
will still include thecreateXXXX
mutation in the GraphQL Schema, and so on.
In the examples below, the name_contains: 'k'
syntax matches the UserWhereInput
GraphQL type for the list.
- For singular
read
/update
/delete
operations, when theGraphQLWhere
clause results in 0 items, anAccessDeniedError
is returned. - For batch
read
operations (eg;query { allUsers }
), when theGraphQLWhere
clause results in 0 items returned, no error is returned. - For
create
operations, anAccessDeniedError
is returned if the operation is set to / returnsfalse
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 },
},
});
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',
},
},
});
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
delete
orauth
access 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:
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.
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: false
in the example above will remove thename
field from theUserUpdateInput
type but may still include the field inUserCreateInput
for example.
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.
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: () => false
in the example above will still include thename
field in theUserUpdateInput
type.
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;
};
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.
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.