Authorization Beyond Abilities0:00
Well, before we jump into token abilities, we need to take a step back and think of just user authorization in general. Because there's so much more to user authorization than just, can a user do something? For example, let's take a look at a ticket. This is a ticket with an ID of 20, and the author has an ID of 1. So in a typical application, we would want to limit who can edit this ticket based upon, well, a few factors. First of all, is it the author? And second of all, is it a manager or an admin? And that's it.
And second of all, is it a manager or an admin? And that's it. No one else should be able to edit this ticket. So it's more than just, can a User do something? It's can a User do something for a particular resource? So then the question becomes, how do we start to implement that? Because we could do all of those checks inside of our controller. We could check the token ability using a tokenCan method. And then we could say that the ability could be ticketUpdate or something like that. But then we also need to make sure that the User is the same User that created that ticket.
And then we could say that the ability could be ticket update or something like that. But then we also need to make sure that the user is the same user that created that ticket. And then if we had any other criteria, we could have that here. But you know, of course, one of the things that I've wanted to do is keep our controllers as lean as possible. So the more criteria we have, the more this is going to grow. And I don't want to do that. So then the question becomes, once again, how do we implement this? And I want to keep all of these things in one place. Because I don't want to have to go to one file to see, okay, is the user authorized
Using Policies for Authorization1:29
And I want to keep all of these things in one place. Because I don't want to have to go to one file to see, okay, is the User authorized here? Then go to another file for some other rules. I just want one place. So one thing that we could use is a policy. Because that ties into the authorize method. So that we could specify an ability and then the model that we wanted to check. And then the policy would determine whether or not the User was authorized to edit that ticket.
And then the policy would determine whether or not the User was authorized to edit that ticket. And if they weren't, it would throw an authorization exception. So that we could catch that and then return a payload that basically said, you are not authorized to update that resource. And that would be a 401. So I like this approach. So let's start implementing that. We can make a policy using php artisan make:policy. And we need to version this because different versions might have different policy requirements.
Registering Versioned Policies2:19
We can make a policy using php artisan make:policy. And we need to version this because different versions might have different policy requirements. So we will create that policy and then we need to register the policy. And we would do that inside of the AuthServiceProvider. There is this policies array to where we would map a policy to a model class. So for our Ticket model, we want to use the TicketPolicy that we just created. Now hopefully this is throwing off some red flags. Because we have just bound this TicketPolicy, this version one TicketPolicy to our Ticket model. And there's nothing that says we can't come in here and say, okay, we have a V2 that we
model. And there's nothing that says we can't come in here and say, okay, we have a V2 that we also want to use. And we can do that just fine. And that's perfectly fine. But this is going to throw a wrench in our plans because the authorized method doesn't really know which policy version that we want to use. Well, we could tell it that. Whenever we call the authorized method, we could pass an array as the second argument where we can specify the ticket and then the policy that we wanted to use.
Creating an isAble Helper3:20
Whenever we call the authorized method, we could pass an array as the second argument where we can specify the ticket and then the policy that we wanted to use. So here we could say ticketPolicy for V1. But I don't want to do that because that means every time we call the authorized method, we would have to do the same thing. And I like the simplicity of just passing the ability and the model. And that's it. So what if we did something like this? We would have a method called isAble. And for all intents and purposes, this is going to be exactly the same as calling the
We would have a method called isAble. And for all intents and purposes, this is going to be exactly the same as calling the authorized method, but it's going to be specialized for our case. So that for our V1TicketController, it would use the V1TicketPolicy. And then we would use the same concept for our AuthorsController. So for our V1AuthorsController, we would have a V1AuthorPolicy. So then we just have to implement this thing. And I think, since we have this base APIController, let's just write that public isAble method here, to where we have the ability and then we have the target model.
let's just write that public isAble method here, to where we have the ability and then we have the target model. But then we also need this to be as flexible as possible, because we need to be able to specify the policy class that we want to use. So let's say that we'll have a protected, we'll just call it policyClass. So that inside of this isAble, we will simply return $this->authorize, we'll pass in the ability. But then for the second argument, we would use that syntax that I briefly showed you before, to where we pass in an array that has the target model and then the policyClass.
showed you before, to where we pass in an array that has the target model and then the policy class. So that means inside of our TicketController, we would need to specify that policy class. So we'll have protected policyClass is equal to the TicketPolicy class. So that inside of our update method, all we have to do is pass in update, pass in ticket. If the $user is authorized to update that ticket, then everything should work. Otherwise, it should fail. So let's try this, first of all, with this ticket with an ID of 20.
Testing and Implementing Policy5:26
Otherwise, it should fail. So let's try this, first of all, with this Ticket with an ID of 20. Because we are signed in with the User with an ID of 1. So let's modify our body, although we need to go to the patch request, don't we? So the Ticket is already canceled. Let's say that we want to complete that. So we will send that request. Hopefully, well, we are not authorized. That's not what I intended to see. I intended to see this working.
That's not what I intended to see. I intended to see this working. So let me make sure everything is okay. We are returning this authorize. We are specifying the ability, target model, and this policy class. We didn't implement the policy. We created it, got ahead of myself. Okay, so inside of our TicketPolicy class, which we've just now opened, we didn't even open it. Isn't that fun?
we didn't even open it. Isn't that fun? What we can do is create a new method that matches the ability that we are checking for. So in this case, it would be update. And here we get two things. We get the user, that is the user that is currently signed in, that is trying to update in this particular case. And then we have the ticket that we are wanting to work with. My gosh, I can't believe I did that.
And then we have the ticket that we are wanting to work with. My gosh, I can't believe I did that. So in this case, our check is going to be very simple. We want to see if the userID is equal to the ticketUserID. Now it'll work. We can go back to Postman, we can send that request, and we can see that, sure enough, it worked. We updated that status to C. But let's try in the case of a ticket that does not belong to our user. And the ticket with an ID of 101 does not.
Verifying Unauthorized Updates7:06
But let's try in the case of a ticket that does not belong to our User. And the ticket with an ID of 101 does not. In fact, I don't even remember what User we assigned to that. So let's do a GET request to see what that User is. And it's a User with an ID of 2. So definitely, this is not going to work. And thankfully, we are getting the results that we expected. So now that we have this policy set up, we can use this for all of our criteria, so that in the next episode, we will assign a token ability and check for the token ability.
all of our criteria, so that in the next episode, we will assign a token ability and check for the token ability.
