Introduction to Payload's Access Control
hey everyone today we're going to go over what I think is one of payload's most powerful features and that is Access Control. payload actually delivers a lot more power than other headless cms's through our access control because it's all code based and it's much more flexible than like an out of the box role-based access control system. Honestly our Access Control makes us more like an application framework than a headless CMS because you can declare such granular control over who can do what with your payload APIs and the admin panel reacts smartly based on what you say users can do and can't do.
So for example if a user logs into the admin UI and they can't update a collection the update button will automatically disappear. If they can't edit certain Fields those fields will automatically appear as read only. and this is powerful it's really simple the best part it's all done in code and not saved in the database.
So what I've done today is I've put together a quick boilerplate to Showcase some of these Access Control features. This is on GitHub I'll put the link in the description to this video but it's an open source repo that just shows how to leverage the power that payload delivers with its Access Control. We're gonna do a bunch of stuff. We're gonna walk through all the high level features that we've built and I hope you like it.
Setting Up the Demo Environment
Okay so I've already got this repo pulled down and I'm going to run yarn Dev seed to get the environment up and running. And I'm going to pop over here and I'm going to show you what the code base looks like at a high level.
So if you're familiar with payload you know about the payload config this is the entry point and we have a couple collections stubbed out here and I'm going to show you how to leverage the access control from maybe like a multi-tenant style infrastructure where you have multiple sites but users will log in and they should only have access to certain sites. and that goes for media that goes for pages that goes for sites themselves. They should only be able to see the access or the site content that belongs to sites that they've been given access to. And then we'll have like an admin role so that the admins can log in and see everything but basically we just have these couple collections here and then we have an automat function to seed the database.
So the seed function is pretty cool because this allows us to get up and running quickly with a Dev environment so we're just going to seed a site one site two create a user create another user that has an editor role and then create a couple pages to Showcase what people can and can't do.
User Collection Access Control
But I'm actually going to start by looking at the users collection. So the user's collection is who is going to log into the admin panel and that's what's going to be basing our Access Control around. and you can see here I've got access set on this user's collection and I'm just going to quickly read through these functions and then we'll dive into each one. But the access for the users collection says that create, only admins can create users and I've got these functions declared in an access folder over here. I'll go through those in a second but these are reusable and you'll see that we reuse these like crazy they're just really simple and clean.
So only admin users can create. Is admin or self for read, so if you're a user you should be able to read your own but if an admin you should be able to read all. and then the same thing goes for update so an admin should be able to update all but a user that's logged in as an editor should only be able to update their own user account. And then finally only admins can delete users.
So let's go through these one by one. is admin, let's click that here. We have is admin and this is an Access Control Function and we are typing it strongly with the access type from payload types. And then we're saying we're destructuring the user, so the currently logged in user off of the the express request and we're saying hey if this user has roles that includes admin then return true. So we're basically going to cast that as a Boolean so if the user rolls includes admin then return true but if it doesn't then return false and that's how we can tell is admin.
So again let me go back here and I can show you this users is admin and then the function says okay return if the user.rolls.includes admin and roles is a custom field that we've added to this admin or to this user's collection here it's called roles. We're going to save this value to the JWT which is the rec.user and it's going to be a select where you can choose many roles. By default it will be set to editor and then we have some field level access control which I'll get back to in a second but then the options so far just to keep it simple are just admin and editor.
Admin vs. Editor User Experience
And this roles field is what we're going to be using to look at is admin and I want to show you that in the browser real quick. If I pull up the admin panel, zoom in a couple times. I'm going to log in as my admin user. This user was seated automatically as an admin right here. so you can see I've passed roles admin and if I log in with Dev at and the password test and I go to my users, I can see that I have two users because I've seeded those two users here. I'm in the admin I should be able to see everything and if I go to my user, I can see that I can edit my first name, last name, I can change my password if I want. I can change my own roles.
But then check this out because I've set the field level Access Control on rolls. I'm saying is admin field level. So that is a function that restricts who can do what to this roles field specifically because look at here if update is declared as is admin or self and same with read, if you're logged in as an editor you should be able to read your own user and you should be able to update your own user but you shouldn't be able to change your own role if you're an editor. So you can see again if I go into this is admin herself this access control is a little bit more complex than our simple is admin. What we've got is okay we're going to destructure the user. if a user, you need to be logged in otherwise you just return false. Nobody can do whatever you're asking to do if there is nobody logged in. But if you are logged in if you're an admin if you have a role for admin then return true. But if you're any other type of user only provide access to themselves. So we're going to return a query constraint here and query constraints are very powerful and payload. This is how you filter down sets of documents and this is crucial to how access control works in payload because you can restrict Which documents someone can perform actions against by returning a query constraint from your Access Control Function. And in this case what I'm saying is return a query constraint where the ID of the document equals the user ID. So this will say provide access only to themselves.
And that's super cool because if I go back to the admin panel and I log in as my site one, this is just another user that I seated here. For, um, it has the editor role access and it's got access to only site one which I'll get back to. but this user if they log in you can see is admin field level so I won't be able to create or update a value here. It'll just take the default value of editor. So if I log into site one and I go to my user, first thing you can see that I can't see the admin user. That's pretty cool I can only see the users that I am able to see. So if I click into site one you can see here that I can change my profile because if we go back to our collection level access control, is admin or self. This is me so I should be able to update it but I can't update the roles or the sites field which the sites I'll get back to but these are grayed out. I can't change these at all but I can change my name. hello. I can do that just fine but I can't change my roles and sites, and the payload UI automatically responded dynamically to show me what I can and can't do. because I'm logged in as site one. Now I'll show you that again I'll log back in with devat which is an admin user. I can do whatever I want to site one. I can change their role, I can give them admin. I can close that, I can give them back editor, and I can change which sites they have access to which is super cool.
Multi-Tenant Site Access Control
But let me log out and the next thing that I've got here to demonstrate kind of like a multi-tenant infrastructure is I've got a sites field. This is a relationship to a collection called sites and this indicates what sites the user should have access to. And in my seed document I've given site one access to the site one user. So but the funny thing is I've actually seeded two sites. So I've got site one and I've got site two. And if I log in as this admin user and I go over to sites, I can create new sites, I can see both sites, I can go in here and I can update this. But if I log out and I log in as the site one user, I'll only be able to see my own site and I can't edit it. Only admins can edit this and that's all done based on the sites field here.
So how does that work? That's pretty cool. Let's go back to our users. You can see the same type of field level access is set on the sites field as well. So only admins can update users roles and sites. But then if I go into the sites collection, I'll show you that the sites collection has a different set of Access Control defined. Only admins can create sites. Read, you should be able to read if you're an admin or you have site access based on the ID field. Only admins can update and only admins can delete. And there's just a simple title field here for now. But that's pretty cool and being able to dynamically change what the admin UI provides to a user means that you can trust not only that the apis are secured but that you have a nice clean experience for your editors logging into the payload admin UI only being able to see what they're given access to see.
Advanced Access: Published vs. Draft Content
So there's a couple more cool things that I've done here. Pages has some cool access control as well. You should see for read it's set is admin or has site access or is published and this function here will restrict what pages a logged in user will be able to see and by default if there is no logged in user then it will only let people see published pages so you can restrict who can see drafts and whatnot. I'll show you that function in a second but if I go over here and I log, well who am I logged in as? Okay cool. If I go to pages, I seated two pages but I can only see the one. Here's site one and here's site two. But because I'm logged in as someone that only has access to the site one I can only see the site One content. If I log in as dev then I can see both pages and I can update them accordingly.
But let's look at this Access Control here because this is pretty cool. This is kind of a crazy name. I'm not a big fan of how I named this function but this is demo so deal with it. This function does a couple things. Number one, if a user is logged in And if the user rolls includes admin return true and admin can do whatever. But if it includes editor and their sites are set then return only Pages where site is in user.sites or site doesn't exist at all. This is a chained where query which is super cool you can generate complex nested and or scenarios to be very very granular with your query constraints. But then if there is no user logged in we're returning a query constraint that says only let non-logged in users read pages where status equals published.
So let me show you that real quick if I go over here and right now I am logged in as who? Dev at. So if I go to localhost 3000 API/pages you can see that I can see both Pages here, one two, there are two pages. But if I open this or if I log out let's say and I refresh I can still see both of these Pages because they're published. But I'm not logged in. But if I log in and I create a draft page let's say. Let's go over here and say this is a draft page and I'll say hello here's some content and then I assign it to site one and I save draft. Notice that this draft page is not published it's set to a draft. And if then I log out, well let me first show you that now we have three here. So we've got three pages including the draft so you can see this is a draft page, status draft. But if I log out, and I refresh this, now I can only see two pages because I'm not authenticated. So that query constraint for non-logged in users restricts who can see unpublished things which is super cool.
Okay now there's one more thing I want to show you and that's how to open up access control so that anybody can create a document in your API. This might be useful for people that should be able to sign up for their own user accounts. This could be useful for people that want to submit contact requests or form submissions of some kind without being authenticated. And to do that all you have to do is just pass a function that instantly returns true. So anyone can create a contact request.
But nobody can update ever and only admins can read these. But no one can delete them ever. And this is just a really simple collection it's only got one field called message. But to demonstrate this what I want to do is hop over to postman. And so Postman I am not authenticated and you can see if I try to post to sites for example from the API. If I go over to sites I have create restricted to is admin which as we know it requires a user to be logged in with the role of admin set. But if I try to post this it'll tell me you're not allowed to perform this action. So the access control is shared from not only the admin panel but also obviously all of the payload apis. So I can't do this one but if I go over to this one where I'm posting to contact requests and I'm sending a message I can hit send and you can see that I've successfully been able to create because I opened up the access control on contact requests. It says true. So if I log into the admin panel again, and I go to devat, I can see contact requests and Bam here's my contact request that someone created. I can't read it, I can't delete it but anybody can create a new one.
But if I, and again I'm logged in as an admin but if I log out, you can if you remember we have read set to is admin so that means that if I log in and I'm not an admin I won't even be able to see these. Payload's admin UI will automatically hide it from the admin panel because I can't see it at all so it's gone. I'm logged in as a regular editor we can't see it. Log out, log back in as an admin, and now I can see it. Pretty cool and anybody can create.
Conclusion and Resources
So this boilerplate that we've created shows how to do all of this. It's pretty simple I think if you pull it down and you look at it yourself you'll be able to familiarize yourself with the concepts but the power is pretty pretty amazing and I'm really proud of what we've been able to do here. But I'd love to know what you think and I hope this video has been helpful to you and we've got a lot more on the way. So thank you and we'll see you soon.