Moving Beyond Roles0:00
Role-based systems are relatively simple to implement, they're relatively simple to understand, and for the most part they work pretty well. But eventually there comes a time when it just isn't feasible, either because maybe you need more granularity, maybe you need more of a sense of actual assigning permissions as opposed to assigning roles, and you know, a lot of times you end up having to manage a lot of roles, at which point you have to think, does this make a lot of sense to manage these roles? Maybe I need to transition away from roles and use, you know, actual permissions, which is what we are going to start doing in this episode. And you know, we could take several different approaches.
is what we are going to start doing in this episode. And you know, we could take several different approaches. We could just wipe everything and start completely over. But in a real-world scenario, we wouldn't be doing that. We would be making incremental changes to update our application to use some other kind of authorization. So that's what we are going to do. But really, everything hinges upon our policy or our gates, because this abstracts away a lot of the underlying checking for permissions. So the first thing that makes sense to me is to make sure that the rest of our application
Switch Routes to Policies1:19
a lot of the underlying checking for permissions. So the first thing that makes sense to me is to make sure that the rest of our application is going to be relying upon our policies or maybe a gate. So by looking at our routes, you know, we are using our role middleware here, and we kind of need to get away from that if we are going to transition away from roles. So instead, what makes sense to me is we would use the can middleware, and then we would just have something like manageArticles, you know, something very simple, so that inside of our ArticlePolicy, we could simply write a method called manageArticles, and all we really need is the $user that we want to check for, in which case, we will check if the $user has any role.
really need is the User that we want to check for, in which case, we will check if the User has any role. And you know, there are several roles, admin, editor, and author. The idea being that we don't really care what they can or can't do at this point in time, we just need to be sure that they can, or at least they should be able to access the articles. So by doing this, we have gotten away from using our role middleware here, but we do need to specify what policy that we need to use for this route, in which case, we can just say app\Models\Article, and that is going to be fine. Now, the reason why we are doing this is because, well again, Laravel needs to be able to
just say app\Models\Article, and that is going to be fine. Now, the reason why we are doing this is because, well again, Laravel needs to be able to resolve our policy with, well, with our policy. So by specifying the model that we want to use here, we are saying Laravel, apply this manageArticles policy, which is what we had just defined. So that is going to work for us right now. Now again, it's okay that we are still relying upon our roles in this case, we are just kind of transitioning the rest of our application from using our custom stuff into using more traditional authorization, which is probably what we should have done to begin with, but oh well.
Define Gate for Users3:16
traditional authorization, which is probably what we should have done to begin with, but oh well. So we have our manageArticles middleware. Now we need to do something about this admin role, because the admin can manage users and roles. So again, I want to get away from using our role middleware, and we could do something like this to where it's simply manageUsers. So that, we don't really have a policy for that, but I think we can go to the AppServiceProvider, and we can just define a gate. Because we only have one thing thus far with that, so we can say manageUsers, and then
provider, and we can just define a gate. Because we only have one thing thus far with that, so we can say manageUsers, and then we of course need the user to check for. But once again, we would be checking if the user has the admin role in this particular case. And that should be fine, so that's back whenever we define these routes, that should work as it should. And we don't have to specify a policy here, because it's not a policy, this is a gate. So we should be fine there. The only other thing I think is inside of the navigation, where we use our roleChecker
So we should be fine there. The only other thing I think is inside of the navigation, where we use our role checker here. So once again, we can use can, but now that we have that gate, we can say manage users. And that should work there, so that we would end can. And I don't think we use that role directive any place else. I think everywhere else is using our policy. Yes, we have the can for the update. Let's scroll on up for the create, and sure enough, that is using our policy there. So I think we are okay to go and proceed with this.
Let's scroll on up for the create, and sure enough, that is using our policy there. So I think we are okay to go and proceed with this. The only other thing I want to do is inside of the AppServiceProvider, let's just get rid of everything that we don't need. So we'll get rid of our role directives, because we are getting away from our role-based authorization. Everything should be okay there. We do, however, need to clear the view cache. So we'll just say php artisan view:clear, and we should be good to go there. So that inside of the browser, we should be able to sign in as admin. We're not gonna check everyone, but we should be able to sign in as admin, and we should
So that inside of the browser, we should be able to sign in as admin. We're not gonna check everyone, but we should be able to sign in as admin, and we should be able to access really everything that we should be able to access. But call to undefined method hasRole, and that is inside of the AppServiceProvider. And of course, it's because I use this instead of User. That's a simple enough fix. We will refresh, and sure enough, we are able to access our articles and our roles. So now that our application depends upon our policy or our gates, we can proceed with transitioning away from our roles and implementing actual permissions. So let's create a new model, and we'll just call it Permission.
Create Permissions Schema5:57
away from our roles and implementing actual permissions. So let's create a new model, and we'll just call it Permission. Or you could call it Claim, or really whatever you want to call it. I'm gonna call it Permission, and we want a migration with that Permission. But we also need to decide how we are going to essentially link the permissions to the users, which with the roles, we used a pivot table, and we could do that just fine. I don't necessarily want to do that in this particular case because it adds some complexity, and we will be incorporating a pivot table later on with something else. So for right now, we are going to need to make another migration, and we'll call this add_permissions_to_users.
So for right now, we are going to need to make another migration, and we'll call this addPermissionsToUsers. So the idea is that we are going to have a single column inside of the users table that is just going to store the permissions. But all we really need to do is create this column as a JSON column. We'll call it permissions, and it can be nullable. And let's go ahead and fill out the down function as well, so that we can drop that column if we need to back out of this migration, and that's going to be fine. But then let's open up the migration for the permissions table.
and that's going to be fine. But then let's open up the migration for the permissions table. And this is going to look a lot like the roles table, in that we are only really going to have two columns where we can have the auth_code. But thinking back from a typing perspective, I don't necessarily like auth_code. It makes sense, but maybe we should have called it name or something along those lines. To keep being consistent, I'm just going to use auth_code there. And then we could have another column that is going to be the description of that permission. So that now we have our permissions table where we will store our permissions, and we will apply those permissions to users with that permissions column.
Seed and Assign Permissions7:53
So that now we have our permissions table where we will store our permissions, and we will apply those permissions to users with that permissions column. So the only other thing that we need to do here is seed our database with our permissions. I'm just going to paste that in because it's quite a bit. But the idea is relatively simple. So I have used a naming convention. And when it comes to defining permissions, it's important to come up with some kind of convention just so that you can get in your mind of how to read those permissions and know what they apply to. So what I have chosen to do is the first token inside of the permission string.
just so that you can get in your mind of how to read those permissions and know what they apply to. So what I have chosen to do is the first token inside of the permission string is the model or the resource or whatever it is that I'm wanting to control permissions for. So we have article:create for creating an article. And then we have article:update, article:updateAny. And these two rules are very similar, but I also want them to be very different because there are some users that would be able to update any article. There are some users who would only be able to update their own. So that's the difference between the two. The update is they can update their own.
So that's the difference between the two. The update is they can update their own. Update any means that they can update any. It doesn't really matter. And we follow through with the delete permissions. But then for users, for right now, we're just going to have create. And for permission, we are just going to have create. But then we need to assign those permissions to the users, in which case the admin, well, they're going to have a lot. And all these are going to be the simple strings.
in which case the admin, well, they're going to have a lot. And all these are going to be the simple strings. We don't need anything else. So these are going to be the auth code. So the admin will be able to create. They will be able to update any. They will be able to delete any. They will also be able to create a User as well as create permissions. And that's going to be okay for right now. But then we need to think about the other users as well, like the author User.
And that's going to be okay for right now. But then we need to think about the other users as well, like the Author User. Well, they will be able to create. They will be able to update and delete, but only their own things. So those are the permissions that we would assign to those users. And then the Editor User, well, they aren't able to create, but they can update any and they can delete any. And then finally, we get to the AuthorEditor, which is a hybrid user. They can create, but they can also update any and they can delete any. Now, you might be thinking this is a lot of work.
They can create, but they can also update any and they can delete any. Now, you might be thinking this is a lot of work. This is very tedious to assign these individual permissions to individual users. Absolutely. But there are things that we can do to alleviate this, which we will talk about in a couple of episodes. Right now, we're just going to stick with these individual permissions. But the idea is that we can get very granular with the permissions that we can assign to users. And that's ultimately going to not necessarily make our application more flexible,
with the permissions that we can assign to Users. And that's ultimately going to not necessarily make our application more flexible, but it's going to give us a lot more control over how we can provide access or deny access to individuals. Now, before we make any changes to our database, we need to go to the User model and we need to make some changes here. For example, we need to add the protected attributes because we do need to specify our permissions. And we'll set the default value to an empty array. And I guess we could also add that permissions to the fillable.
And we'll set the default value to an empty array. And I guess we could also add that permissions to the fillable. So let's do that. We also need to set up the, not the hidden, but the casts. So that permissions is an array. And by typing permission or permissions, I know inside of the database seeder, I used permission there. So let's change those back to permissions. And now we should be able to see the database. And the first thing I'm going to do is php artisan db:wipe.
And now we should be able to see the database. And the first thing I'm going to do is php artisan db:wipe. Then we will run the migrations. Then we will seed the database and everything went okay. And so now that we have these permissions in place, you know, we could do something like this. Let's put all this roles stuff down a little bit. And what we will essentially do is we'll start off with the hasPermission. Eventually we'll implement the hasAnyPermission, but the idea is going to be so that we will return
Update Models and Policies12:20
Eventually we'll implement the hasAnyPermission, but the Idea is going to be so that we will return if the provided permission is in our permissions, but we do need to call strtolower because all of our permissions are in lower case. So that we will search inside of our permissions array. And then this is going to allow us easy use to check if a User has a given permission. So for managing articles, oh, we really need to implement the hasAnyPermission, don't we?
So for managing articles, oh, we really need to implement the hasAnyPermission, don't we? So let's do this. We'll just copy this to where we will hasAnyPermission. We are going to get an array of permissions. And we essentially want to do what we did for the hasAnyRoles. But in this case, it's going to be a little bit easier because right now we aren't going to do anything with the context. It's going to change. But for right now, this is going to be fine.
It's going to change. But for right now, this is going to be fine. So inside of the hasAnyPermission, let's see. We want to map strtolower over the permissions that were passed. And we want to see if there are any matches inside of our permissions array. And then we will simply return those matches. So that inside of our ArticlePolicy, we can now check if we have any of the following permissions, which can be a little long because some users can create, some users can update, some users can delete,
which can be a little long because some users can create, some users can update, some users can delete, but then some users can only update any. And we should also check for article delete any. Let's format this so that it makes it a little bit easier to read. And I think that should cover all of our bases. If we can create, update, delete, update any, and delete any, then we can manage our articles. So that's whenever we attempt to access the articles route, then we can see we have access.
So that's whenever we attempt to access the articles route, then we can see we have access. So that's good. Inside of our AppServiceProvider where we defined our manageUsers gate, we can change this. Instead of hasRole, we will say hasPermission. And we want to check if the User can create a User. And I know that there are other things involved, but I think for right now, this is going to be fine. Although we do have the hasAnyPermission, don't we?
but I think for right now, this is going to be fine. Although we do have the hasAny permission, don't we? So we might as well just go ahead and use that so that we can also check if the user can create permissions. Once again, inside of the browser, when we refresh, we still see that we can access the users route and the roles route. So then it's just a matter of changing all of our other policies. For example, for viewAny, we had the role of admin or editor, but that essentially translates to several permissions. So we will check if the user has any permission.
but that essentially translates to several permissions. So we will check if the user has any permission. And I'm using this instead of user. That is a bad habit of mine. But if the user has article create, article update any, and delete any, then they can view any article. Then for the create policy, we will check if the user has any permission. And all we need in this case is article create. But that's just one. We just need to check if the user has that permission,
But that's just one. We just need to check if the user has that permission, and that's going to be fine. Essentially, the same thing needs to be done for our next check, if the user can update. In which case, we just need to check if the user can update any. Then we, of course, allow them. Otherwise, if the user has the permission of article update, and the user ID is the same as the article ID, then they are allowed.
and the user ID is the same as the article ID, then they are allowed. Otherwise, we deny as not found. And it's essentially the same thing for the delete, except that we check for the delete permissions. So now we can just straight up copy and paste. We'll change this to delete any and article delete. And with that simple change, we have transitioned away from roles. But this is just the tip of the iceberg, because when you start to incorporate actual permissions,
But this is just the tip of the iceberg, because when you start to incorporate actual permissions, things can get out of hand really quickly. I mean, as you've seen, we assigned several permissions to an individual User, like the admin User has several permissions. And there's nothing wrong with that. But from a management perspective, that's not great. But we could create groups that we assign permissions to, and then we can assign Users to those groups, which we will start looking at in the next episode.
and then we can assign User to those groups, which we will start looking at in the next episode.
