A common pattern is Role-Based Access Control. Here, we'll walk you through how to create your own RBAC pattern on both the collection-level and field-level.
In more detail, here are the pieces that we will be building:
We'll be using
create-payload-app to build out the initial project.
npx create-payload-app payload-rbac
blankfor our template
This will give us a simple project with a Payload config and Users collection. The structure of the project will be:
First, we will add the
role field to our Users collection with 2 options:
Next, we will create a new
Orders.js collection in our
collections/ directory and scaffold out basic fields and values - including the
createdBy relationship to the user.
The Orders collection has an
array field for items and a
createdBy field which is a relationship to our
Users collection. The
createdBy field will feature a strict
update access control function so that it can never be changed.
Notice we also have a
condition function under the
createdBy field's access. This will hide
createdBy until it has a value.
createdByAttribute Using a Hook
Next, we'll add a hook that will run before any order is created. This is done by adding a
beforeChange hook to our collection definition.
The logic in this hook sets the
createdBy field to be the current user's
id value, only if it is on a
create operation. This will create a relationship between an order and the user who created it.
Next, the access control for the collection can be defined. Payload's access control is based on functions. An access control function returns either a
boolean value to allow/disallow access or it returns a query constraint that filters the data.
We want our function to handle a few scenarios:
Once defined, this function is added to the
access property of the collection definition:
With this function added to the
delete access properties, the function will run whenever these operations are attempted on the collection.
The last step is to add the collection to our
Let's verify the functionality:
Start up the project by running
npm run dev or
yarn dev and navigate to
Create your initial user with the
Create an Order with the
Create an additional user with the
user role by navigating to the Users collection, selecting Create New, entering an email/password, then saving.
Log out of your
admin user by selecting the icon in the bottom left, then log in with the second user.
You'll notice if we go to the Orders collection, no Orders will be shown. This indicates that the access control is working properly.
Create another Order. Note that the current user will be saved to
Created By in the sidebar.
Navigate back to Orders list on the dashboard. There will only be the single order created by the current user.
Log out, then back in with your
admin user. You should be able to see the original Order as well as the Order created by the second user.
With everything working at the collection level, we can carry the concepts further and see how they can be applied at the field level. Suppose we wanted to add a
paymentID field only for Admin users. Create an
isAdmin function that checks the role as we did earlier.
Add a new field to Orders and set
update access calls to use the isAdmin function.
The new paymentID field is not available to the users even on one's own Order. Field level access controls allow for greater granularity over document level access for Collections and Globals. This shows how easy it is to manage exact permissions throughout the admin UI, GraphQL and REST endpoints; it even works when querying relationships to keep data secure.
Now that we have a basic example working. What are some ways that this could be improved?
editorrole which allows reading and editing, but disallows creating. This all can be customized specifically to your needs.
I hope you enjoyed the introduction to doing role-based access control with Payload!
Come join the Payload discussions on GitHub.