Sep 16, 2013
Table of contents:
Last week we created the functionality to allow people to register and authenticate with Cribbb. Nearly all web applications require authentication in some form or another. Authentication is not just about restricting access to certain pages, but also restricting access to a wide array of user abilities.
In order to write good web applications, it’s important to abstract this kind of functionality. You shouldn’t have the logic to allow or disallow access to certain pages or methods scattered around your project because it will make maintaining it a nightmare.
For example, imagine the criteria for authenticating had to change in your web application, but the logic for determining access was scattered around hundreds of files. It is inevitable that you will forget to update certain aspects of your code, which could leave your application exposed to an attacker.
Laravel 4 deals with this problem by abstracting this logic into Filters. Filters allow you to restrict access to a given route. This means it is trivial to only allow admins to access the admin section of your website, or only allow registered user to POST
a new piece of content.
In this article we will be exploring the full functionality and usage of Laravel filters to see how they can keep our application safe and secure.
A filter is basically a chunk of code that you will typically want to run either before or after Laravel routes a request.
So for example, if you wanted to only allow registered users to access a certain route, you could attach an authentication filter that would determine if the current user is authenticated.
When Laravel attempts to route the user to the request page, Laravel will automatically run your filter logic before accessing the route.
If a filter is returned a response, Laravel will action that response instead of the original request. So to protect restricted routes to only registered users, you would check to see if the current user is a guest. If the user is a guest you would return a redirect response to the login page.
So as you can see, a filter is just an abstracted piece of logic that can be run before or after a request is routed through the application.
Laravel ships with a number of helpful filters already available to you. If you open up app/filters.php
you will see a list of the pre-set filters.
The first two Filters you will see are the application Before and After filters:
App::before(function ($request) {
//
});
App::after(function ($request, $response) {
//
});
These two filters allow you to register code that you want to run before or after each request. These two application events are part of the lifecycle of each Laravel request. So for example, if you wanted to record access requests to a log file, or you wanted to poll a services for updates you could have this logic run automatically on every request.
In order to understand how a filter works, let’s break one down by look at one of the most important, the authentication filter:
Route::filter("auth", function () {
if (Auth::guest()) {
return Redirect::guest("login");
}
});
The filter
method on the Route
class accepts two arguments.
The first argument is the name of the filter, in this case auth. We need to give a filter a name so we can later attach it to a route.
The second argument is a Closure (What are PHP Lambdas and Closures?). The Closure is where you store the logic for this particular filter.
So in the authentication filter above, Laravel checks to see if the current user is a guest. If the current user is a guest, the filter returns a redirect to the login page. If a filter returns a response, Laravel will route the request to the response, rather than the initial request.
Once you have the filter set up, you need to attach it to a route in order for it to take effect.
To attach a filter, simply pass it as an argument in the array of the second argument of a Route
method definition:
Route::get("user/account", [
"before" => "auth",
"uses" => "UserController@account",
"as" => "user.account",
]);
In the example above I’m attaching the auth filter to ensure only registered users can access the user account page.
To attach multiple filters, simply separate them with a pipe:
Route::get("user/premium", [
"before" => "auth|premium",
"uses" => "UserController@premium",
"as" => "user.premium",
]);
In this example I’m checking that the user is both authenticated and is also a premium subscriber.
An important thing to note about using multiple filters is, if the first filter fails, all subsequent filters will be cancelled. This makes sense in this situation because there is no point in checking to see if the current user is a premium subscriber if they are not authenticated. However, this is important to remember for some routing and filtering situations.
Attaching filters to each of your routes is going to get cumbersome pretty quickly. Fortunately, Laravel offers two ways to prevent unnecessary repetition.
The first method is to use a Pattern Filter:
Route::filter("admin", function () {
// Is this an admin?
});
Route::when("admin/*", "admin");
In this example all URLS that are namespaced under the *admin/** root will automatically have the admin
filter applied.
You may also restrict pattern filters to HTTP methods:
Route::when("post/*", "auth", ["post", "put", "delete"]);
In this example only authenticated users would be able to create, edit or delete posts from the application.
Using a route pattern is perfect when you want to attach a filter to a very specific set of routes like above. However it’s often the case that your routes won’t fit into a nice pattern and so you would end up with multiple pattern definitions to cover all eventualities.
A better solution is to use Group Filters:
Route::group(["before" => "auth"], function () {
Route::get("user/account", "UserController@account");
Route::get("user/settings", "UserController@settings");
Route::get("post/create", "PostController@create");
Route::post("post/store", "PostController@store");
// ...
});
In this example all of the routes within the group will automatically have the authentication filter applied to them.
In all of the examples above, we used Closures to hold the logic of the filter. Laravel also allows you to create a specific class for your custom defined filter.
Why would you want to do this? Well if you have many particular complex filters, it will probably make sense to abstract them away from the filters.php
file to prevent that file from getting messy. This will make organising and maintaining your filters a lot easier.
Filter classes also use Laravel’s IoC Container. This means that they will automatically be able to use dependency injection so you can very easily test that they are working correctly.
An example of a filter class could be:
class ApiFilter {
public function filter()
{
if (!$this->valid($access_token)
return Response::json(array(
'error' => 'Your access token is not valid'
), 403);
}
}
You can then register you class based filter like this:
Route::filter("api.auth", "ApiFilter");
Filters allow you to very easily abstract complex route access logic into concise and easy to use nuggets of code. This allows you to define the logic once, but then apply it to many different routes.
99% of all projects will end up using filters in one way or another. Whilst I’ve focused primarily on authentication in this tutorial, filters can be applied in a wide variety of situations where you want to restrict access to a certain route based upon predefined logic or you want to run a predefined chunk of code before or after certain requests.
So instead of scattering this sort of logic throughout your application, abstract it to a filter that can be defined once and be reused. This will not only make your code clearer, but it will also be more maintainable and less likely to go wrong in the future if you are required to change the logic.
This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.