تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Controller Authorization Checks0:00

Okay, here's another one that you'll run into all the time. And if you're not careful, it can really bloat your controllers. So take a look. We have this concept of a Team and adding a User to the Team. So I've set that up with a TeamMembersController in a store method, where we automatically pass through the Team itself. All right, this is fine. So you would write the logic to add the User to the Team and essentially create that association. But the reality is there's a number of checks you often need to do before you get to that point. For example, if you are not signed in, no way, you can't do this. So if you are not signed in, no way. What else? Well, another condition is if you are not the owner of the Team, you can't perform this action. Only the creator of the Team can add a User to the Team. So if you are not the owner of the Team, no way. Okay, and then maybe let's do one more check. Let's say your Team can be

this action. Only the creator of the team can add a user to the team. So if you are not the owner of the team, no way. Okay, and then maybe let's do one more check. Let's say your team can be maxed out. I have this concept at Lerikas. So if your team size is 5, and you're already maxed out, and you try to add another team member, no way, you can't do that. If the team is maxed out, no way. All right, so we have three checks here that must be performed before I can even get to the part where we physically add the user to the team. Now, in terms of logic, I'm going to be a little more verbose here just to demonstrate a couple things. So one, if you are a guest, if auth guest, no way. So we abort with a unauthorized 403, you are not signed in. Okay, next one. If you are not the owner of the team, once again, let's do this in a verbose way. So if the team's ownerId does not equal the authenticated user's id, then no way. So abort with a 403,

you are not the owner of the team, once again, let's do this in a verbose way. So if the team's ownerId does not equal the authenticated user's id, then no way. So abort with a 403, you are not the owner of this team. Finally, if the team is maxedOut, I'm going to simulate this, we're going to call a method isMaxedOut. And we'll let that do the logic. Once again, 403, your team is maxed out. Now in reality, you may be catching these exceptions and redirecting somewhere. But this will be a good start. Okay, so let's go ahead and create this method. I'll switch to my Team class, I have a new method isMaxedOut. And I'm just going to stub it out and we'll say false for now. Alright, so if I come back, yeah, take a look all of this crap, all of this authorization junk you have to do before you can get to the meat of what you're actually trying to accomplish. And worse, think it's possible that in other areas of this controller,

Creating a Custom Policy2:29

all of this authorization junk you have to do before you can get to the meat of what you're actually trying to accomplish. And worse, think it's possible that in other areas of this controller, you may need to perform the exact same checks. So what exactly is the solution? Well, we can use a policy object for this. And once again, I'm going to show you two options. One, just the homegrown way of creating a class and inserting this logic. And then to Laravel itself offers a policy functionality that we can reference. Okay, so let's see for the homegrown route, we could create an app/Policies directory. And within it, maybe there's an AddTeamMemberPolicy. All right, let's set that up. namespace will be App\Policies, and class AddTeamMemberPolicy. Next, maybe our contract for all of these policies is a allow method. So here's what we could do, we can come back, I will take all of this junk here, yank it and commented here for

policy. Next, maybe our contract for all of these policies is an allow method. So here's what we could do, we can come back, I will take all of this junk here, yank it and commented here for the time being. And we'll come back and I'm going to paste it in here. Now it looks like we do need the team. So I can either pass it through to the allow method, or maybe allows, we'll call it that. Or we could inject it through the constructor. So maybe we should do that one. And I will assign that. And let's see protected team. Okay, so now you new up the class, you feed it the team. And then you ask the class or the policy, if it allows the given action. And in this case, that action is adding a team member. Okay, let's try it back to our TeamMembersController, I can just say, new AddTeamMemberPolicy, feed it the team. And then let's wrap this check to see if it allows the current action. Okay, so let me import this at the very top.

I can just say, new AddTeamMemberPolicy, feed it the team. And then let's wrap this check to see if it allows the current action. Okay, so let me import this at the very top. And hopefully that makes sense, right? We're really just creating a simple class. Sometimes folks get so obsessed with the actual pattern name. So it's like, you're afraid to create a class unless it conforms to this pattern. So it has to be a service class. Because otherwise, what would it be? You know? And the answer is, it's it's just a class. That's it. These pattern names maybe help describe what it does. But at the end of the day, it's a class. That's it. So in our case, in order to add a member to a team, we're going to reference our new AddTeamMemberPolicy. And we check to see if that policy and that action is allowed. Now, if we switch over there, we can see that does the exact same checks we were doing before. And it throws exceptions in

Testing Policy Outcomes4:58

member policy. And we check to see if that policy and that action is allowed. Now, if we switch over there, we can see that does the exact same checks we were doing before. And it throws exceptions in this case. Okay, so final step, and then we'll take a look at this in the browser. Let's make sure all of our team references are referring to the property. Or again, you can pass it through the allows method if you prefer. Anyways, and then we'll import the team itself. Okay, let's take a look in Chrome. Now, in this case, it looks like everything checks out. So we have our little hint here that says, yeah, go ahead and add the user, however you need to. But now, if I go to my routes file at the top, you can see I'm just stepping out a authenticated user. Let's comment that out and ensure that we have no user at all. Let's see what we get. Back to Chrome. Refresh. We are not signed in. Cool. Next, what if we sign in a different user?

Let's comment that out and ensure that we have no User at all. Let's see what we get. Back to Chrome. Refresh. We are not signed in. Cool. Next, what if we sign in a different User who is not the owner of the team? Okay. Well, if we come back, now we get another one. You are not the owner of the team. Finally, let's go to the Team class itself and assume that the available spaces have been filled. So the team is in fact maxed out. Okay, now we'll get another one. Okay, so we can see we have all of this specific business logic that determines if we can add a new team member. And what's cool is we've now, and by the way, I'm going to undo that back to false. But anyways, the cool thing is now we've taken that logic and we've isolated it behind a policy with a readable name. And further, this can be reused anywhere. Now, if I go back to my TeamMembersController, take a look. All of this junk we had before that we may have had to

Using Laravel Policies6:32

a policy with a readable name. And further, this can be reused anywhere. Now, if I go back to my TeamMembersController, take a look. All of this junk we had before that we may have had to duplicate or at the very least move to a protected method on the controller. Yeah, that can all be deleted entirely in place of this. Sweet. Okay, but now I did say Laravel has its own concept of policies. So what would that look like? Well, let me show you. If I run php artisan, you'll see that it has a make command for policies right here. So let's try it. php artisan make:policy, and this is a policy for teams. Now, quick note, before in our case, we had sort of like a single use policy. So this class is only responsible for the policy of adding a User to a Team. And we can continue doing that. But in this case, we're going to use a more general TeamPolicy. And that way it can be responsible for anything related to working with a team. Because often,

And we can continue doing that. But in this case, we're going to use a more general TeamPolicy. And that way it can be responsible for anything related to working with a team. Because often, the logic is nothing more than a single line. So like if the ownerId is equal to the authenticated user's id, we're good to go, you know, so you can group that stuff together. All right. So we will now see that if I give this a refresh, right here in our policies directory. So let's open that up and take a look. Now you'll see it's kind of similar, we have some nice functionality out of the box. So we are going to add a policy for storing a new TeamMember. And actually, you know what, it just occurred to me, this is really a TeamMemberPolicy. So let me rename that real quick. Now, in our case, we're not going to inject anything through the constructor, so I can delete that. Next, any of Laravel's policies will accept the authenticated user,

that real quick. Now, in our case, we're not going to inject anything through the constructor, so I can delete that. Next, any of Laravel's policies will accept the authenticated user, as well as anything else you want to pass in. So in our case, I know we're going to pass in the team. So let's inject those or I'm sorry, import those App\Team, and also App\User. Okay, so now if we switch back here, here's our sort of makeshift class, I'm now going to take all of this out once again, and move it over here. And we'll paste it in. But now this time, once again, we can bring this back just to the team variable. Now there's actually some more cool stuff we can tweak in just a minute. But let's see if this works first. Now how do we register a policy with Laravel? We do that in the AuthServiceProvider. So app/Providers/AuthServiceProvider.php. And notice here, we can register any number of policies and associate them with the given model.

with Laravel? We do that in the AuthServiceProvider. So app/Providers, AuthServiceProvider. And notice here, we can register any number of policies and associate them with the given model. So for example, the Team model, we're going to associate that with app/Policies, TeamMemberPolicy. And yeah, it's possible that TeamPolicy is all you need for everything. I created a TeamMemberPolicy only because there might be a special logic, but if not, TeamPolicy is great. So now think of this as just a very simple lookup. So when we try to authorize a Team, it will know, okay, I'm going to reference this TeamMemberPolicy here. What's the next step? Well, if we go back to TeamMembersController, now we're going to remove our makeshift class. And instead say, and actually, you can do this in a number of different ways. I think there's actually too many ways to do it, to be honest. But pick one or two and stick with it. The one I like

And instead say, and actually, you can do this in a number of different ways. I think there's actually too many ways to do it, to be honest. But pick one or two and stick with it. The one I like is to call an authorized method. Now you can reference the method on the policy you want to call, like update or store or something like that. Or you can let it be dynamic. And basically, that would take the shape of this. So if we call authorize and we just pass in the team, and we exclude that initial argument of the method name, basically, what's going to happen is Laravel will see, oh, your method is store. So maybe you want a store method on your policy, in which case it will call this. So it's just sort of a magic dynamic thing. If you like it, stick with it. If you want to be more explicit, call it directly. Either option works. Okay, so let's try this again. Let me reformat. We call an authorized method. Behind

If you like it, stick with it. If you want to be more explicit, call it directly. Either option works. Okay, so let's try this again. Let me reformat. We call an authorized method. Behind the scenes, Laravel knows to reference our teamMemberPolicy and call the store method. But now, what about the user? Remember, we only called the authorized method with the team object. Well, behind the scenes, Laravel is going to pass through the user for you. And it's also going to do one other thing that I'll show you in just a minute. So back to Chrome, give it a refresh, and we get this action is unauthorized. So basically, what happened here is, well, you can do two things. One, you can return a Boolean. True or false, does the user have authorization to do this? But there's also maybe situations where you do want to throw an exception so that you can catch it up the chain, maybe in your controller. For example,

Refining Policy Logic12:08

you get the proper error. If we go to Team and the team is maxed out, you get the proper error. And then finally, if the user isn't signed in at all, we should get the proper error. Refresh. This action is unauthorized. But notice something. This is the final thing I want to show you. Within here, we never get to this point right here. So for example, if I say caught, you'll notice we never get there. And that's because behind the scenes, if you are not signed in, Laravel will automatically catch that and you'll never even get to this point. So that means you get this check for free for every single policy you create. So I can delete that one entirely with the understanding that if I get to this point, at the very least, I do have an authenticated user. Next, I could clean this up by, well, taking this kind of confusing conditional and figuring out what I'm really saying here.

at the very least, I do have an authenticated User. Next, I could clean this up by, well, taking this kind of confusing conditional and figuring out what I'm really saying here. And I think it's this. I'm saying if the Team is owned by the given User, or really, if the Team is not owned by the given User, then sorry, you can't do this. All right. So why don't we create that method on the Team? And yeah, I think this is a good practice to get into. You take those confusing conditionals that you might repeat throughout the entire project, and you move it behind the class that is responsible for the logic. So a Team, yeah, it makes sense that a Team can know if it is owned by the given User. So now I would say return this Team's owner ID and tell me if it is equal to the ID of the passed in User. Now we've cleaned that up just a little bit more. As such, to add a new member to a Team, you must be authenticated, you must own the Team,

and tell me if it is equal to the ID of the past User. Now we've cleaned that up just a little bit more. As such, to add a new member to a Team, you must be authenticated, you must own the team, and the team must not be maxed out. So now if we do a final check, we should get the exact same thing. Okay. But you know what? Actually, one more thing if you want to stick around. Let's go to another team. So team with an ID of two is owned by the User with an ID of two. Just accept that. Okay. So in this case, sure enough, we are getting the proper exception that, once again, you can catch from your controller. But now what if you want to say the administrator has a User ID of one, and they can do anything under the sun they want. They have no restrictions. They are the administrator. Okay. Well, take a look at this. We're going to assume that the User with an ID of one is the administrator. This is the admin.

Admin Overrides with Gate14:32

They have no restrictions. They are the administrator. Okay. Well, take a look at this. We're going to assume that the User with an ID of one is the administrator. This is the admin. Okay. Well, that means even though they didn't create the Team, they still have authorization to add a member. All right. Well, here's how we can do that. If I go to my TeamMemberPolicy, we could do it here by creating a before method, and this will accept the user. And then I could say if user->id equals one, or once again, you take this magic number and this logic, and you move it behind a method. So if the user is an administrator, then they are authorized instantly. So yeah, Laravel will call this before method, and if you return true, that means we don't even need to get to this point. You already are authorized. Okay. Real quick, to the User model, create is admin. This is a very primitive way of checking, but it's good for small projects.

don't even need to get to this point. You already are authorized. Okay. Real quick, to the User model, create is admin. This is a very primitive way of checking, but it's good for small projects. Now we should be good to go. So refresh. I'm the administrator, so I always have authorization no matter what. But now, well, what about this? If we go back, I'm having to add this in a lot of different policies because basically I want to say if you're the administrator, there are zero restrictions for you. Right? Okay. Well, if that's the case, I can get this out of the TeamMemberPolicy. And if I switch back to my AuthServiceProvider, down in the boot method, here's where we cycle through and we register all of these policies. But I can also say Gate::before and then do the exact same thing here. If $user is admin, return true. And now this will be applied globally. So refresh. We have authorization. But if we comment that out, you are no longer

then do the exact same thing here. If $user is admin, return true. And now this will be applied globally. So refresh. We have authorization. But if we comment that out, you are no longer authorized. So we see the proper exception. All right. I hope you liked that one. I'll see you in the next video.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟