Sanity Access Control: moving from `_.groups` to the new APIs
One of the perks of Sanity's enterprise plan is the ability to customize very thoroughly the access control.
Our team maintains a marketplace where customers buy products from different sellers. We use custom access control to let sellers edit their own products descriptions. We certainly don't want them to edit other products or documents. Setting this up has been a lot of trial and errors, hence this guide.
Up until now, we have been using groups to set up custom access control rules. Groups are stored as regular Sanity documents in each dataset, and can be modified using mutations like any other document. Note that only create session tokens have the permission to create and update group documents.
This is how our groups looks like:
{
"_id": "_.groups.seller-access.product-name",
"_type": "system.group",
"grants": [
{
"filter": "_id in [\"drafts.XXX\", \"XXX\"]",
"permissions": ["read", "create", "update"]
}
],
"members": ["pk1234abc"],
// updatedAt, _rev, etc.
}
Our user, with the id pk1234abc
has the "Custom" role because it gives no permission by default.
The group above is then giving this user permission to create, read, and update the document with id XXX
.
This setup works for us, but it has 2 main drawbacks:
- We need a
create-session
token to manipulate group documents, which means we cannot build a UI inside Sanity studio using the current logged in users to create and update the groups. Not all our employees handling access control are comfortable using command line tools (or even tools like Postman). - All our members who are sellers appears in the Sanity.io user interface to manage project members with the "Custom" role. It's impossible to see easily who has access to which document.
In June this year, the Sanity team introduced Improved Roles & Project Management. It gives enterprise customers access to HTTP APIs to customize access control.
Pros:
- You can customize access control at several levels: organization, project or dataset.
- You only need an admin token, so you can build a specific UI in the studio (that's what we did).
- New roles are properly named in Sanity.io UI (we aren't limited to "Custom" anymore).
Cons:
- The API documentation is a bit light.
- The migration path from groups to new roles API is a bit unclear. We now have 2 different ways to restrict access to members.
- New vocabulary to learn (roles, grants, permission, permissions resources, filters, etc.).
- Hiding custom panels in the studio based on access control requires a lot of code or workarounds.
The first step is to create a custom role. It only needs a title, a name, and optionally a description.
curl \
-X POST \
-H "Authorization: Bearer ${token}"
-H "Content-Type: application/json"
-d '{"title": "Product X Owner", "name": "product-x-owner", "description": "Members allowed to update the details about product X"}'
https://api.sanity.io/v2021-06-07/projects/${projectId}/roles
We now have a new role that can even be assigned from Sanity.io user interface, but it currently has no granted permissions.
We now need to create new rules to link to our new role. The list of all permission resources can be found by sending a GET request to the /projects/${projectId}/permissionResourceSchemas
endpoint. They are endless possibilities!
With our goal to only give access to a specific set of documents, we can use the sanity.document.filter
schema.
curl \
-X POST
-H "Authorization: Bearer ${token}"
-H "Content-Type: application/json"
-d '{"permissionResourceType": "sanity.document.filter", "title": "Access to product X", "description": "Only access to product X", "config": {"filter": "_id in [\"${document-id}\", \"drafts.${document-id}\"]" }}'
https://api.sanity.io/v2021-06-07/projects/${projectId}/permissionResources
We just create a "permission resource" to restrict access to the document with the id document-id
and its draft. This new resource has an id
(appearing in the request response) that we will use in the next step. Because yes, this is not enough, we now need to link our new role, our new resource and a grant (like read
, update
, etc.)
Let's give our new role read
access to our new permissions resource:
curl \
-X POST
-H "Authorization: Bearer ${token}"
-H "Content-Type: application/json"
-d '{"roleName": "product-x-owner", "permissionName": "read", "permissionResourceId": "${idReturnedInPreviousSection}"}'
https://api.sanity.io/v2021-06-07/projects/${projectId}/grants
In our case, we want to repeat this request to assign the update
and create
role too.
For this you can either use the user interface on Sanity.io OR use the API:
curl \
-X POST
-H "Authorization: Bearer ${token}"
-H "Content-Type: application/json"
-d '{"roleName": "product-x-owner"}'
https://api.sanity.io/v2021-06-07/projects/${projectId}/acl/${userId}
Deep down inside the documentation for Sanity UI, there is an article about Building a custom tool with Sanity UI. It was the perfect opportunity for us to test the new access control API AND Sanity UI at the same time. Unfortunately, we cannot distribute this plugin as it's highly tailored to our use case. And there is a huge drawback to, which is that we cannot restrict which members can see this tool and access it in the toolbar. As a workaround, we display a warning message when a member with insufficient access levels tries to access it.
Sanity – build remarkable experiences at scale
Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators that’s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.
Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.