Skip to content
KeystoneJS LogoKeystoneJSv5.x alpha

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.

Table of Contents

GraphQL Access Control

There are two ways of specifying Access Control:

  1. List level
  2. Field level

Defaults

To set defaults for all lists & fields, use the defaultAccess config when creating a Keystone instance:

const keystone = new Keystone('My App', {
  // Initial values shown here:
  defaultAccess: {
    list: true,
    field: true,
  },
  // ...
});

List level access control

List level access control can have varying degrees of specificity depending on how much control you need.

Access API

A key on the list config, access can be specified either as a single control, covering all CRUD operations, or as an object keyed by CRUD operation names.

There are 3 ways to define the values of access, in order of flexibility:

  1. Static
  2. Imperative
  3. Declarative

Described as a Flow type, it looks like this:

type GraphQLWhere = {}; // fake/placeholder

type AccessInput = {
  authentication: {
    item?: {},
    listKey?: string,
  },
  listKey?: string,
  operation?: string,
  originalInput?: {},
  gqlName?: string,
  itemId?: string,
  itemIds?: [string],
};

type StaticAccess = boolean;
type ImperativeAccess = AccessInput => boolean;
type DeclarativeAccess = GraphQLWhere | (AccessInput => GraphQLWhere);

type ListConfig = {
  access:
    | StaticAccess
    | ImperativeAccess
    | {
        create?: StaticAccess | ImperativeAccess,
        read?: StaticAccess | ImperativeAccess | DeclarativeAccess,
        update?: StaticAccess | ImperativeAccess | DeclarativeAccess,
        delete?: StaticAccess | ImperativeAccess | DeclarativeAccess,
      },
  // ...
};

GraphQLWhere matches the where clause on the GraphQl type. ie; for a list User, it would match the input type UserWhereInput.

AccessInput function parameter

  • authentication describes the currently authenticated user.
    • .item is the details of the current user. Will be undefined for anonymous users.
    • .listKey is the list key of the currently authenticated user. Will be undefined for anonymous users.
  • listKey is the key of the list being operated on.
  • operation is the CRUDA operation being peformed ('create', 'read', 'update', 'delete', 'auth').
  • originalInput for create & update mutations, this is the data as passed in the mutation.
  • gqlName is the name of the query or mutation which triggered the access check
  • itemId is the id of the item being updated/deleted in singular update and delete operations.
  • itemIds are the ids of the items being updated/deleted in multiple update and delete operations.

When resolving StaticAccess;

  • true: Allow access
  • false: Do not allow access

Definition of access operations:

  • 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

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 given DeclarativeAccess - it does not make sense to do so and will throw an error if attempted.

Let's break it down into concrete examples:

Booleans
Shorthand static Boolean
keystone.createList('User', {
  access: true,

  fields: {
    // ...
  },
});

Great for blanket access control for lists you want everyone/no one to see.

NOTE: When set to false, the list queries/mutations/types will not be included in the GraphQL schema.

Granular static Booleans
keystone.createList('User', {
  access: {
    create: true,
    read: true,
    update: true,
    delete: true,
  },

  fields: {
    // ...
  },
});

Use when you need some more fine grained control over what actions users can perform.

NOTE: When set to false, the list queries/mutations/types exclusive to that operation will not be included in the GraphQL schema. For example, setting create: false will cause the createXXXX mutation to be excluded from the schema, update: false will cause the updateXXXX mutation to be excluded, and so on.

Shorthand Imperative Boolean
keystone.createList('User', {
  access: ({ authentication: { item, listKey } }) => {
    return true;
  },

  fields: {
    // ...
  },
});

Enables turning access on/off based on the currently authenticated user.

NOTE: Even when returning false, the queries/mutations/types will be included in the GraphQL Schema.

Granular functions returning Boolean
keystone.createList('User', {
  access: {
    create: ({ authentication: { item, listKey } }) => true,
    read: ({ authentication: { item, listKey } }) => true,
    update: ({ authentication: { item, listKey } }) => true,
    delete: ({ authentication: { item, listKey } }) => true,
  },

  fields: {
    // ...
  },
});

Use when you need some more fine grained control over what actions some or all anonymous/authenticated users can perform.

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 the createXXXX 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.

NOTES:

  1. For singular read/update/delete operations, when the GraphQLWhere clause results in 0 items, an AccessDeniedError is returned.
  2. For batch read operations (eg; query { allUsers }), when the GraphQLWhere clause results in 0 items returned, no error is returned.
  3. For create operations, an AccessDeniedError is returned if the operation is set to / returns false
Granular static GraphQLWheres
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 what items a user can perform actions on.

Granular functions returning GraphQLWhere
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',
    },
    // ...
  },
});

Use when you need some more fine grained control over which items and actions anonymous/authenticated users can perform.

Field level access control

access API

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.

There are 2 ways to define the values of access, in order of flexibility:

  1. Static
  2. Imperative

Described as a Flow type, it looks like this:

type AccessInput = {
  authentication: {
    item?: {},
    listKey?: string,
  },
  listKey?: string,
  fieldKey?: string,
  originalInput?: {},
  existingItem?: {},
  operation?: string,
  gqlName?: string,
  itemId?: string,
  itemIds?: [string],
};

type StaticAccess = boolean;
type ImperativeAccess = AccessInput => boolean;

type FieldConfig = {
  access:
    | StaticAccess
    | ImperativeAccess
    | {
        create?: StaticAccess | ImperativeAccess,
        read?: StaticAccess | ImperativeAccess,
        update?: StaticAccess | ImperativeAccess,
      },
  // ...
};

NOTE: Unlike List level access, it is not possible to specify a Declarative where clause for Field level access.

NOTE: Fields do not have a delete access controls - this control exists on the list level only (it's not possible to 'delete' an existing field value - only to modify it).

AccessInput function parameter

  • authentication describes the currently authenticated user.
    • .item is the details of the current user. Will be undefined for anonymous users.
    • .listKey is the list key of the currently authenticated user. Will be undefined for anonymous users.
  • listKey is the key of the list being operated on.
  • fieldKey is the key of the field being operated on.
  • originalInputis the data as passed in the mutation for create & update mutations (undefined for read).
  • existingItem is the existing item this field belongs to for update mutations & read queries (undefined for create).
  • operation is the CRUDA operation being peformed ('create', 'read', 'update', 'delete', 'auth').
  • gqlName is the name of the query or mutation which triggered the access check
  • itemId is the id of the item being updated/deleted in singular update and delete operations.
  • itemIds are the ids of the items being updated/deleted in multiple update and delete operations.

When defining StaticAccess;

  • true: Allow access
  • false: Do not allow access

Definition of access operations:

  • 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
keystone.createList('User', {
  fields: {
    name: {
      type: Text,
      access: true,
    },
  },
});

Great for blanket access control for fields you want everyone/no one to see.

NOTE: When set to false, the list queries/mutations/types will not include this field in the GraphQL schema.

Granular static Booleans
keystone.createList('User', {
  fields: {
    name: {
      type: Text,
      access: {
        create: true,
        read: true,
        update: true,
      },
    },
  },
});

Use when you need some more fine grained control over what actions users can perform with this field.

NOTE: When set to false, this field will not be included in GraphQL queries/mutations/types exclusively used by that operation. Eg, setting update: false in the example above will remove the name field from the UserUpdateInput type but may still include the field in UserCreateInput for example.

Shorthand Imperative Boolean
keystone.createList('User', {
  fields: {
    name: {
      type: Text,
      access: ({ authentication: { item, listKey }, existingItem }) => {
        return true;
      },
    },
  },
});

Enables turning access on/off based on the currently authenticated user.

NOTE: Even when returning false, the queries/mutations/types will include the field in the GraphQL Schema.

Granular functions returning Boolean
keystone.createList('User', {
  access: {
    create: ({ authentication: { item, listKey }, existingItem }) => true,
    read: ({ authentication: { item, listKey }, existingItem }) => true,
    update: ({ authentication: { item, listKey }, existingItem }) => true,
  },

  fields: {
    // ...
  },
});

Use when you need some more fine grained control over what actions some or all anonymous/authenticated users can perform.

NOTE: Even when returning false, this field will be included in GraphQL queries/mutations/types exclusively used by that operation. Eg, setting update: () => false in the example above will still include the name field in the UserUpdateInput type.

Have you found a mistake, something that is missing, or could be improved on this page? Please edit the Markdown file on GitHub and submit a PR with your changes.

Edit Page