Missing Authorization Check2:10
So although the update book link has been removed from the page, they haven't checked permissions on this page itself, which means that us accessing the page is allowing us to edit and modify the Book, which we're not allowed to do. And that's what I mean by missing authorization. The authorization check required to check if the current User, i.e. the Steven user, has the permissions to edit this Book are missing, and so Steven should not be allowed to edit this Book because they're not the maintainer of the Book. So what we're going to do is jump into our code and then add an authorization gate in order to protect this route from being accessed by the Steven user, by the user that isn't the maintainer.
Defining an Authorization Gate2:42
order to protect this route from being accessed by the Steven user, by the user that isn't the maintainer. So we'll jump to the code. Okay, so we're in the AuthServiceProvider in our application and within the boot method. So this is where we can define our authorization gates, which is where we will set the different abilities and authorization rules that we want for the application. So we'll add in the authorization gate, so we'll go Gate::define, updateBook. Okay, so what we're saying here is we're defining a gate called updateBook, so that is the ability, that's what it's called, and then we've got the callback, which is what is executed when we're checking if we have this ability.
ability, that's what it's called, and then we've got the callback, which is what is executed when we're checking if we have this ability. So the first parameter for the callback is the current User that's automatically passed in. So we'll go $user, $user, and this is the User that we're checking the ability for, and then any other values are additional values that are passed in when checking the authorization, in which case, in this case we want checkBook. Okay, so that's our callback. Now we need to define our rule. So the way that gates work in authorization is they check the value that is returned from
Now we need to define our rule. So the way that gates work in authorization is they check the value that is returned from it to see if authorization is allowed, and we can simply use a boolean in this case. So I can do return $book->$user == $user. And what this will say is it will check if the $user is the user set on the $book, which is the maintainer of the book, and that will return a true or a false, depending on if the $user is a maintainer or not. So now we've got the gate defined, we need to go into our controller and then tell it to do the authorization to actually check if the $user has permissions on the $book. So I'll jump over to the BookController, and we'll go up into edit, and up in here,
Authorizing in Controller4:14
to do the authorization to actually check if the user has permissions on the book. So I'll jump over to the BookController, and we'll go up into edit, and up in here, because this edit route is where it's loading the edit form, we want to block access to the edit form. So I'll go this->authorize('update', book), it's the same name we call it, the same ability that we set on the Gate, and then we can do book here. So what we're saying here, do you authorize the current user to update book, which is the ability, and we're giving it the book parameter as the additional parameter. Now I'll also paste that into the update method as well, because we want to use it in both ends.
Now I'll also paste that into the update method as well, because we want to use it in both ends. And I have seen this before on some sites that I've worked on, where the form has been hidden behind authorization, and so you couldn't load the form, but the update method that was actually doing the work, or the create method that was doing the work, didn't have authorization. Which meant that you could bypass the form really easily, you could use a different form and modify it, or you could just send a direct request and not bother with the form, and you could bypass the authorization check and make the changes on the server. So you need to make sure that you're applying authorization on both sides, both on the form.
Testing the Gate5:45
So our gate is now blocking requests to this page, which is exactly what we want. Now we need to make sure that it's actually working, we're not just blocking everything, because that would be bad and defeat the purpose of using the gate. So jump back over to the main page, and we'll go into the Lord of the Rings, and then we'll go Update Book. And as we can see here, we can still edit this page, edit this book, because we're the maintainer. So you can see that our gate is doing its job, it's doing exactly what it's supposed to be doing, and protecting the routes that we're not supposed to have access to. Now gates are fantastic and they make it really easy to set up authorization, but the problem
Replacing Gate with Policy6:11
to be doing, and protecting the routes that we're not supposed to have access to. Now gates are fantastic and they make it really easy to set up authorization, but the problem is when you have lots of resources and lots of different permissions, gates can get very messy because you would have a different callback for every single one. And that's where policy objects come in, so model policy objects. And we're going to replace our gate now with a model policy object, which will handle the authorization and then we can expand on that later if we need to. So we'll jump over into our terminal. Okay so in the terminal, what we want to do is use php artisan to make our BookPolicy, which we can then update in the code editor.
Okay so in the terminal, what we want to do is use php artisan to make our BookPolicy, which we can then update in the code editor. So we'll go php artisan make:policy BookPolicy Book. So we're telling it to make a new policy, and we're telling it the model we want for the policy is the Book model, and then we're going to do BookPolicy here as the name of the policy. So we'll run that, and then we'll jump over into the code, and in here we're going to open up the newly created BookPolicy. Okay, fantastic. So this is our BookPolicy.
Okay, fantastic. So this is our Book policy. I'll just collapse everything down so it's a bit easier to see, and as we can see here, because we've told it the model we're using is the Book model, it has given us a bunch of default CRUD methods. So all we have to do is fill out the ones that we are working with. So in this case, what we want to do is fix up the update method to put our gate logic in here. So we'll jump over to the AuthServiceProvider, and we'll grab the line out of the middle of the updateBook gate, and we'll delete that.
So we'll jump over to the AuthServiceProvider, and we'll grab the line out of the middle of the updateBook gate, and we'll delete that. So the gate is no longer here, we'll go back to our BookPolicy, and we'll put it in here. We don't have to make any changes, we can still return Boolean responses, and it's still going to work. And now the update is going through the policy rather than through a gate. We can also return response objects as well in here, which give you more control. So for example, we could do AuthAccessResponseAllow, or we could do AuthAccessResponseDenyIsNotFound. So what we're saying here is that if the user owns the book, then we're going to allow it through the responseAllow.
So what we're saying here is that if the User owns the Book, then we're going to allow it through the response allow. If the User doesn't own the Book, then we're going to deny the request, and we're going to throw a 404 instead of a 403. So 404 is a not found error, as opposed to a 403, which is an unauthenticated error. And you would want to change the responses like this if you don't want to reveal the existence of pages. Now in this example, it's not a very good example for this case, because we know it's the edit form. But if you're doing it for when you're mainly loading a resource, and not a specific sub-page.
the edit form. But if you're doing it for when you're mainly loading a resource, and not a specific sub-page like this, then throwing a 404 actually makes a lot of sense, because it hides the existence of things, which makes it harder to do recon and learn more about what's in the database and what's there. So now we've updated our policy, we need to go and change the BookController so that it actually uses the policy. So go to the BookController, and in BookController, here's our authorize line, and what I want to do is we want to change the name. So this is no longer the ability, because we removed the gate, we can just get rid of
to do is we want to change the name. So this is no longer the ability, because we removed the gate, we can just get rid of that and go Update, and then Book. And what Laravel will do, it will automatically detect the Book model we're passing in, and it will look for a book policy. And if there is a method on the book policy that matches the ability here, which is Update, then it's going to check that for our authorization. So this should just work, we should be able to go back to the browser, and then we should be able to see those different errors. So we're going to the browser, we're at Lord of the Rings again, and I'll click Refresh.
be able to see those different errors. So we're going to the browser, we're at Lord of the Rings again, and I'll click Refresh. It's still working, fantastic. Then we'll go to Eye of the World, and now we have the 404 that I was describing before. So our authorization is still working, we are still being protected from accessing these routes. So as I said before, the great thing about using a model policy, policy object, is that you can define all the different policy rules in one class, and then if you have to change any of them at any time, you can easily do that through the one file.
you can define all the different policy rules in one class, and then if you have to change any of them at any time, you can easily do that through the one file. Now something else I've talked about a couple of times is that it's easy to forget to run authorization in the controller, so let's open it up again. And if we look in the controller, we can see here that for each of these methods, we have to put our authorize function call. And it's really easy to forget to include that. And I'm speaking that from experience, both myself, I've forgotten it a few times, and from the sites that I've audited and worked on, it's really easy for them to forget to include it.
Moving Authorization to Routes10:35
from the sites that I've audited and worked on, it's really easy for them to forget to include it. You're adding a new controller, you're adding all these methods, and then you just forget that one line. So this is why I prefer to define authorization rules in the routes file as opposed to the controller. So we'll get rid of these two lines, and then we'll go to our routes file. Where's it gone? Here we go. Okay, so these are the routes for our BookController, and what we're going to do now
Here we go. Okay, so these are the routes for our BookController, and what we're going to do now is modify the edit and update routes to use our policy in order to add authorization in the route. And we can do that using middleware, so I can do middleware here, then Laravel by default gives you a can middleware which is used for authorization, so it links up to the policies and the gates, the same as the authorize method in the controller. So we can do here can update book, and what we're telling it here is that the current user can only access this route if they can update, which is the ability, the book parameter in the URL, which relates to the Book and it will check it's the model and it will automatically
user can only access this route if they can update, which is the ability, the book parameter in the URL, which relates to the book and it will check it's the model and it will automatically match it up to the book policy. So Laravel will do a whole bunch of this magic in the background to ensure that this simple line here is defining the right authorization rules. So if we go back to the browser, we'll refresh the page again, we've still got app 404, and we've still got the form loading for Lord of the Rings, so it is working, our authorization is working through the route. Now the reason why I love using middleware for authorization is that it's really easy to see.
Now the reason why I love using middleware for authorization is that it's really easy to see. We can look at this page and we can see easily that middleware is there, that middleware is on those routes. And it's much harder to forget because you can see the same pattern, you don't have the other logic in the controller actions that are populating your view. And the other thing you can do with middleware is use route groups, and this is where it gets really helpful. So I'll get rid of this line, and we'll move this one up here, and then what I'm going to do, route group, whoops, put that in, then I'm going to move these in here.
So I'll get rid of this line, and we'll move this one up here, and then what I'm going to do, routeGroup, whoops, put that in, then I'm going to move these in here. So what I've done now is our edit and update routes are both sitting within this group, which has the middleware for our authorization wrapped around it. But the benefit of this is if we need to add a new route, say we want to add, like, delete or something, well, we'd be going in here and we'd be adding a new route here, and you can see what happened. Because we're adding the route next to our update route within the middleware group, we automatically have the right middleware in place. Now, granted, update is different to delete, so we would have to change that, but it still
we automatically have the right middleware in place. Now, granted, update is different to delete, so we would have to change that, but it still keeps it protected, while if we were adding it as a separate method on the, a separate action on the controller, it might be harder to remember to put the authorization function in there. Now, of course, we can take it one step further. So if you're using resource controllers in Laravel, you don't have all of this. Instead, what you've got is, what is it, resource. Instead, what you've got is something that looks like this, which controls your resource through a simple line in your routes.
Instead, what you've got is something that looks like this, which controls your resource through a simple line in your routes. But the problem with this is you can't add your middleware onto it for your authorization anymore. If we go to the BookController, so in the BookController, what we can do to use it as a resource controller and add our authorization middleware, is we can add a constructor, and in here we can add authorizeResource. So we go authorizeResource and then book. So what we're telling it here is we want to authorize the resource book from the route parameter book, and then apply that to all of the methods in here, following the standard.
So what we're telling it here is we want to authorize the resource Book from the route parameter book, and then apply that to all of the methods in here, following the standard CRUD methods that the resource controllers all default to. And if we jump over to the browser, we can see once again that we're getting the 404 and we're getting the Lord of the Rings loading. So the middleware is being applied. Now, granted, it doesn't give you the benefit of having it in the routes file, and I wish there was a way to do it in the routes file, but in lieu of that, you still have it in one place on the controller, and if you're working with the different methods in the controller, you're not going to forget it because you don't need to include it in those.
one place on the controller, and if you're working with the different methods in the controller, you're not going to forget it because you don't need to include it in those methods if they're all encapsulated in the resource controller.
