Role-Based Authorization Overview 0:00Now, let's take things up a notch and review role-based authorization. For example, you have users in your system, but each of those users plays a certain role, don't they? So, for example, maybe John plays the role of the moderator, and he's allowed to moderate the forum and that alone. Sally is a manager. She can moderate the forum, but maybe she can also publish content or view trends and things like that. And then finally, maybe Frank is the owner. He, of course, can do everything, but maybe he can also review financial reports, whichAnd then finally, maybe Frank is the owner. He, of course, can do everything, but maybe he can also review financial reports, which no other role can play. So now, we have these roles defined, and each of those roles includes certain abilities, right? So, moderator can edit the forum, among other things. Maybe the owner can view financial reports. You get the idea. And if you've ever worked with WordPress before, this will actually be pretty familiar to you. All right, so users play roles, and each of those roles comes with certain abilities. Creating Roles Schema 0:57And if you've ever worked with WordPress before, this will actually be pretty familiar to you. All right, so users play roles, and each of those roles comes with certain abilities. I think we're ready to get started. We'll begin with a migration. I'm going to put all of these in the same file. Make a migration called createRolesTables, and the table to begin with is roles. Let's go there now, and let's begin. So I'm not going to have a down method here. I find I create those less and less. When I want to make a change, I will create a new migration and make the change there.I find I create those less and less. When I want to make a change, I will create a new migration and make the change there. Okay, but anyways, so if a user plays a role in your system, well, of course, we begin with a big increments. Next, we need the name of the role, like moderator or manager or subscriber, things like that. That'll be a simple string. Then optionally, let's set up a custom label for that. There might be situations where the name of the role is a little bit different from the label, the thing that you want to display to the user. So let's offer that, but it's not required.label, the thing that you want to display to the user. So let's offer that, but it's not required. Maybe if you don't include a label, we will capitalize the name and be ready to go. All right, finally, let's include some timestamps, and that'll be it for roles. All right, so roles include certain abilities, so let's do that one as well. In the past, I've called this permissions. I like abilities as well, basically the same thing. All right, so an ability has a name, like edit form, and once again, the label could be something different, like edit the form or manage form, whatever you want. So these tables are actually pretty similar. Building Pivot Tables 2:29be something different, like edit the form or manage form, whatever you want. So these tables are actually pretty similar. Okay, but now, of course, we have a mini-to-mini relationship here. So we need a table to store the connection between a particular role and a particular ability, because any role can have many abilities, and any ability can have many roles. So I'm going to duplicate this one more time, and the standard naming convention that Laravel uses is the name of the two tables, singular and alphabetical order. So in this case, it would be ability role. All right, let's start from scratch. First, let's grab the role.All right, let's start from scratch. First, let's grab the role. So this would be unsigned big integer. We need a reference to the role ID. We need a reference to the ability ID. If you want, we can include timestamps here. And then finally, we can set up the foreign constraints. So I can say the role ID is actually a foreign key that references the ID column on the roles table. Now, if you happen to delete that role, let's cascade and delete any of these records asroles table. Now, if you happen to delete that role, let's cascade and delete any of these records as well, and then the exact same thing for the ability ID. That references the ID column on the abilities table. Okay, final step here for the primary key, we could do this in a couple ways, but you might consider making it the combination of the role ID as well as the ability ID. That way, those two must be unique. Otherwise, you could do a typical primary key and then set role ID and ability ID to unique. Okay, so we're almost done.unique. Okay, so we're almost done. But think about it. We need one more thing. If a user can have any number of roles, which is very common. Well, we need one more pivot table, a table to store the user ID and the role ID associated with them. So we can copy this one more time, paste it in. And this will be for the alphabetical order user role user. And then let's see, we need the user ID in question, the role ID it corresponds to.And this will be for the alphabetical order user role user. And then let's see, we need the user ID in question, the role ID it corresponds to. And then let's move this up. All right. The user ID references the ID column on the users table, all the same stuff here. And then finally, user ID and role ID. And I think we're good to go here. So let's go ahead and give this a migrate. Ah, it fails. Base table or view not found roles.Ah, it fails. Base table or view not found roles. What? Oh, you know what it is? It's the scaffolding here. Of course, in all of these cases, we are creating new tables. Sorry about that. Let's give it another run. And there we go. So if I switch to SQL Pro, and if we give this a refresh, we're all set to go. Defining Eloquent Relationships 5:13And there we go. So if I switch to SQL Pro, and if we give this a refresh, we're all set to go. So again, now we have users and those users play certain roles in our system. Those are wired up through this pivot table. And then those roles come with certain abilities, which are wired up through this pivot table. OK, now we're ready to create those eloquent relationships. So let's think about it. We have a first a role that a user plays. Next, we have a ability associated with that role. And maybe we're OK for a minute.Next, we have a ability associated with that role. And maybe we're OK for a minute. So let's go to our rules table. Imagine you have a certain role like moderator. Well, again, that has certain abilities associated with it. So let's set up that relationship abilities. And this will be a belongs to many relationship. Like so. All right. Next, let's switch over to the ability and do the inverse.All right. Next, let's switch over to the ability and do the inverse. If you have a particular ability and you want to grab all roles that include that ability, that too will be a belongs to relationship. OK, next, I'll switch to the user model. And if we scroll down, yeah, again, let's say you have a user and you want to grab all of the roles assigned to them. We want this relationship roles. And then one more time. OK, but next, it would be useful if I could assign a role to a user.And then one more time. OK, but next, it would be useful if I could assign a role to a user. So, well, of course, we would ultimately do something like this. Save the role. Right. Let's give that a nice name, like user grant role or assign a role to a user. That's not too bad. Let's try that. Assign role. This would accept some kind of role.Assign role. This would accept some kind of role. We're not sure if that's a string or an object or both. So we'd say this roles save. We'll assume it's an object for the time being and it'll be something like that. All right. Let's have a look. Users have roles and can be assigned to them. Each role has certain abilities and actually the same thing. If we have a role and we want to assign a new ability, maybe we could do something likeEach role has certain abilities and actually the same thing. If we have a role and we want to assign a new ability, maybe we could do something like this, allow to and then we accept the ability. And that would be very similar. This abilities save the ability. OK, so now a role has abilities and can allow a new ability. And then lastly, the ability, which again is edit the forum or publish content or delete records, things like that. Each ability is associated with roles. Now, one last thing here that I often forget, because we have timestamps declared in our Testing Roles and Abilities 7:55Each ability is associated with roles. Now, one last thing here that I often forget, because we have timestamps declared in our migration, we need to explicitly apply those. So we would do it here and then right up here and then finally on our user model. I almost always forget that the first time we're all set to go. So let's play around. Let's boot up PHP Artisan Tinker. Now, I already have some users, right? So let's work with myself in this case. That's the user with a ID of 13.So let's work with myself in this case. That's the user with a ID of 13. And actually, we can continue doing it here. It's very similar, but I have an app called Tinker Well, which is sort of like the Tinker command on steroids as a Mac app. So you can follow along in either. OK, track down the user. And of course, at the moment, the user has no roles. But whoops, already our first mistake. Yep, right here.But whoops, already our first mistake. Yep, right here. Sorry about that. Belongs to many, of course, just like the others. So if we give it another run, of course, the user has no roles. So let's create a new role here. And the name will be, let's start with moderator. OK, but if we run this, we're going to get a mass assignment exception. OK, let's go back to role. And I will often set the fillable fields to or guarded fields to null.OK, let's go back to role. And I will often set the fillable fields to or guarded fields to null. Because I'm happy handling that on my own. You could also extend from a parent model where you set this globally. Just be careful and be sure you know what you're doing. OK, let's come back, give it another run. And now that works. So we have our role. Let's now set up a new ability. This is very similar, but the ability will be, in this case, how aboutLet's now set up a new ability. This is very similar, but the ability will be, in this case, how about report spam or edit forum, however specific you'd like to be. All right, great. So if I switch to SQL Pro, we have users who play roles, which have certain abilities. Which have certain abilities. However, the connection between the role and the ability is not yet defined. OK, well, we know just to refresh your memory in the role model, we added this method called allow to. This is what we want.we added this method called allow to. This is what we want. Role allow to ability. So think about it. If this was called, if we made this specific like edit forum, this ends up reading rather well. Role allow to edit forum or change it to moderator. We're just being super specific here and you get the idea. Now, if we run this and switch back to SQL Pro, we now have a connection between a role and an ability.Now, if we run this and switch back to SQL Pro, we now have a connection between a role and an ability. Now, let's assign a role to a given user. So we already have my existing user here. And if we switch back to PhpStorm on our user model, we added a method here. So I could say, I lost my role. Let's do this. There we go. User assign role moderator. And we'll give that one a run.User assign role moderator. And we'll give that one a run. Great. So now we have my user with an ID of 13, a role with an ID of 5, and a connection between the two. All right, so now check it out. If I want to figure out all roles assigned to this current user, we get one. Next, if we want to grab the first one there and find all abilities for that role, run it again, we get one as well. But now on that note, one more thing.run it again, we get one as well. But now on that note, one more thing. I'd love to do something along the lines of this. Give me all abilities for the user. And maybe that'll be a simple collection or array of ability names that might be useful. So if I run this, of course, it's not going to work. Let's see if we can make it work. But first, how would we grab the abilities? Well, that will give us a collection of roles. But again, we don't quite care about the abilities.Well, that will give us a collection of roles. But again, we don't quite care about the abilities. And yeah, you might think of some kind of has many through relationship to get from a user through the roles, through the role user, all the way to the abilities. Because remember, there's not a direct link between an ability and a user. But I think that might get a little tricky. So why don't we do it in two steps? We'll grab the roles relationship. And then why don't we map over each role and return the abilities associated with that role. And if you're not familiar with the syntax, it's higher order collections.And then why don't we map over each role and return the abilities associated with that role. And if you're not familiar with the syntax, it's higher order collections. Go to Lericast and search for that. Plenty of lessons on it. This will basically call map on a collection and for each one, call the abilities method on the role instance there. And then it will return a new collection. So if we give that a run, now we have a collection of collections for each role. But I don't quite want that. So why don't we flatten it down to a single collection?But I don't quite want that. So why don't we flatten it down to a single collection? And then I don't need the full ability instance. So I will pluck the name. Getting close. And then just to be safe, let's grab only the unique ones. And that is what I want here. OK, so let's switch back to PhpStorm. Go to my user model. If I want to grab, we'll do it down here.Go to my user model. If I want to grab, we'll do it down here. If I want to grab all of the abilities, yeah, this isn't an eloquent relationship. So we have to call it like a standard method. But we could do something like this. All right. So now check it out. If I say user, give me their abilities and run it. This is what we get. And that's what we want. Wiring Abilities into Gates 13:20This is what we get. And that's what we want. All right. So we're about ready to wire this all up. First, let me show you how we're going to do it. In our welcome view, this is the standard welcome to Laravel page. Let's get rid of all of this. OK, so let's imagine you have a list of links. And one of them will be to edit the form. However, of course, you should only see this link if you have permission to edit the form.And one of them will be to edit the form. However, of course, you should only see this link if you have permission to edit the form. Now, we had these abilities, but I still want to wrap it within Laravel's gate functionality. So, for example, I still like the can directive. Can edit the form. I still want this to work. So let's make sure we hook these abilities into Laravel's gate functionality. First, if we run this in the browser, we don't see any links. And this makes sense for two reasons. One, we haven't defined this ability name.And this makes sense for two reasons. One, we haven't defined this ability name. And two, we're a guest in this fresh application. And as we know, guests by default don't have access. OK, let's declare this ability name just like we did a couple episodes ago. So I'm going to scroll down right here to the boot method. And yeah, in the past, we did things like this. Gate define. So what you could imagine doing is something like grabbing the authenticated user's abilities. And then for each one, basically dynamically defining with the ability name.So what you could imagine doing is something like grabbing the authenticated user's abilities. And then for each one, basically dynamically defining with the ability name. And you get the idea. However, there might be some issues there because at this point, we don't have access to the authenticated user. So instead, let's set a global gate before filter. And I learned this from Joseph Silbert. So it's a great idea. So let's say gate before. We're going to run this before any authorization.So let's say gate before. We're going to run this before any authorization. Of course, that will accept the user as well as the ability name. Because remember, that ability would be passed through here. Edit form. OK, so now at the point this callback is triggered, we have an authenticated user. This is the person who is signed in. OK, but now how do we check if the user qualifies for this ability? Well, think about it. We have an array of all of their abilities, right?Well, think about it. We have an array of all of their abilities, right? So why don't we just read that array and check if it contains the current ability and return the result. It's so easy. So now if we come back to Firefox and give it a refresh, remember, it's still not going to work because we're not signed in. Let's simulate it. Again, it's a fresh layer of lab. We don't even have an authentication system.Again, it's a fresh layer of lab. We don't even have an authentication system. So I'll just say auth login using ID. And for my test record, it is 13. OK, now I bet it's going to work. Ta-da! All that work for one link. But it works. And it works because I have been assigned the role of moderator. Now, if I were to remove that, I no longer have that role,And it works because I have been assigned the role of moderator. Now, if I were to remove that, I no longer have that role, which means I no longer have the ability to edit the form. Come back, refresh, and now I don't see it. OK, let's do this. Let's undo a few steps back to where we create a role and assign it to the user. However, on that note, I want to show you one more thing. If I run it again, yeah, I expected this. We get an error. And that's because it's trying to assign the role two times.We get an error. And that's because it's trying to assign the role two times. So it's trying to create another record here. And it's going, hey, we got an issue here with the primary key. OK, so in situations like this, I'll show you a little tip here. If I go back to my user model right here, yes, of course, we could check, well, if there is already a role assigned to the user, don't do anything. But sometimes what I'll do is call sync. And what this will do is basically replace all of the existing records in the pivot table with this collection.And what this will do is basically replace all of the existing records in the pivot table with this collection. And any that are not included in this collection but are in the database will be dropped. But in this case, I don't want to drop anything. So I'm going to set this to false. And if you take a look at this, should we detach any records not included? By default, that's set to true. If I set it to false, though, it'll add any new records if necessary. But it won't drop anything. And that's kind of what we want.But it won't drop anything. And that's kind of what we want. So now if I run this again, even though we already have a record, it won't fail. OK, great. So I'm going to create a new one here called manager. Next, let's add a ability. And as being a manager, maybe you can view reports. All right, so user assign the role. We've already done that. Let's say view reports and update this.We've already done that. Let's say view reports and update this. OK, manager allow to view reports. It's always nice when that reads nicely and give that a run. All right, so take a look. We have users. We have two different roles. The manager role with an ID of six right here has an ability with an ID of five. So if we now switch back to our welcome page, let's add another one here. If and only if you can view reports, we'll add a link to view the reports.So if we now switch back to our welcome page, let's add another one here. If and only if you can view reports, we'll add a link to view the reports. All right, so if I come back and give this a refresh, there we go. I have permission to edit the forum, but not to view reports. So I can't see those. All right, two more things and then I promise we're done. So if I switch back to Tinkerwell, this runs again, we get the exact same issue there. So let's make sure we switch to sync there as well. If I have a role, let's call sync here. It's an easy way to solve this problem.If I have a role, let's call sync here. It's an easy way to solve this problem. Okay, now run it again and we remove the error. So finally, if we switch back to our welcome view, don't forget to lock down the endpoints. So maybe we have one called reports and we'll define that really quick. So if you visit slash reports, we will return the secret reports. All right, so at the moment, anyone can access this endpoint. Let's lock it down. You can only access this endpoint if you can perform a certain ability, which is view reports. And that's it.You can only access this endpoint if you can perform a certain ability, which is view reports. And that's it. So come back, give it a refresh, and now we get a 403 unauthorized. Let's finish up by granting this role to my user. So we have me, actually one thing. So yes, we could say user assign role of manager. But this is assuming I have a role instance and I won't always. So it would be nice if I could say user assign role manager and that just works. However, if I run it, it's not going to work. That's our final step and then we'll be done.However, if I run it, it's not going to work. That's our final step and then we'll be done. We'll go to our user model. And yeah, here we were expecting a role instance. But why don't we say, well, if what you gave us is a string, then let's track down the role. So role where name is role, first or fail, like so. And then we will sync it up. So now back in Tinker Well, if we run it, that works as well. So now myself has a role of five and six. So I'm playing the role of moderator and manager, which is fine.So now myself has a role of five and six. So I'm playing the role of moderator and manager, which is fine. So again, back in Firefox, if I give it a refresh, I am authorized now. So I can access that report. And then lastly, just quick thing on the role model. Let's do the same thing here. So if you have a role and you want to allow to edit the form, we can accept an object or a string. Like that. Update it and we should be good to go. So we crammed a lot in here.Update it and we should be good to go. So we crammed a lot in here. The secret sauce, though, is we create those abilities and then we hook them into Laravel's gate functionality using a global before hook or filter. So you could extract this to your own package if you want, or there's no shortage of authorization packages in the Laravel community. So you might review those as well. Thanks for coming along.