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
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
.
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: false
will cause thecreateXXXX
mutation to be excluded from the schema,update: false
will cause theupdateXXXX
mutation 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: () => false
will still include thecreateXXXX
mutation 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
/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
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
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:
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: false
in the example above will remove thename
field from theUserUpdateInput
type but may still include the field inUserCreateInput
for 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: () => false
in the example above will still include thename
field in theUserUpdateInput
type.
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