Camron Cade

Artisan web development, mostly using Laravel.

Disabling CSRF for Specific Routes - Laravel 5

Update: If you are working on Laravel 5.1, there is support for this right out of the box: See this post for more information

I've been working with Laravel 5 lately, and it's great. But with a new version comes new defaults. CSRF protection for instance, is now always active with the implementation of Middleware, where in Laravel 4, it was something you "turned-on" as you needed it. In my opinion, this is a great move for Laravel, making it more secure out-of-the-box.

However, there may come a time when you want to exclude specific routes and requests from worrying about looking for a CSRF token. (In my case, I had a POST route that was used exclusively as a callback for a third-party API. It uses a different form of authentication that service renders CSRF not only unnecessary, but a hinderence. This is what I did to "disable" CSRF for specic routes.

Middleware

Laravel 5 comes with middleware. It replaces L4's filters and while they are fundamentally different, for the purpose of this guide, you can pretty much treat them as such. (For a more in-depth look at Middleware, check out Laravel 5.0 - Middleware (Filter-style))

If you're not intimately familiar with middleware, that's okay. This is also going to serve as a mini-intro to working with middleware.

How Laravel 5 Handles CSRF

In laravel 5, there is a middleware class, Illuminate\Foundation\Http\Middleware\VerifyCsrfToken with a method handle($request, Closure $closure) that is exceuted every request. It is here that it either lets the request continue on to the controller, or it throws a TokenMismatchException. We want to alter this process so that it skips CSRF protection for routes of our choosing.

Take a look at the included VerifyCsrfToken class. You can find it in the folder vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/. The only public function is handle($request, Closure $next) and it looks like this:

public function handle($request, Closure $next)  
{
    if ($this->isReading($request) || $this->tokensMatch($request))
    {
        return $this->addCookieToResponse($request, $next($request));
    }

    throw new TokenMismatchException;
}

You can see that unless $this->isReading($request) OR $this->tokensMatch($request) return true, a TokenMismatchException is thrown. Otherwise, the request coninues normally. Take a look at what each of these methods do.

The isReading() method just checks for "reading" verbs: HEAD, GET, OPTIONS. If one of these is being used, it skips over the CSRF check.

The tokensMatch($request) closure is called if isReading($request) returns false. This does the work of actually deciding whether a token was included in the request, and if so, checks whether or not it matches the one stored in the session.

What we are going to do

Okay. Time to work. We are going to add a third condition in that if statment, called $this->excludedRoutes($request). But we don't want to modify the class we've been looking at. That clas is managed by composer and would get overwritten.

We are going to create a new class that *extends* the VerifyCsrfToken class. I'm going to put mine in `App/Http/Middleware` and I'm going to name it `VerifyCsrfMiddleware.php`. The class, blank and extending VerifyCsrfToken looks like this:
<?php namespace App\Http\Middleware;

class VerifyCsrfToken extends \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken {

}
Because we extended the Laravel default middleware class, we have access to all of it's public and protected methods. Yes, that includes `tokensMatch($request)` and `isReading($request)`. When a middleware class is called, Laravel looks for the the `handle($request, Closue $closure)` method, so we need to create that. We also want to just add an additional `OR` condition to the existing `handle($request, Closure $closure)` in the class we are extending. So go ahead and copy that into *your* blank middleware class, and let's add a `$this->excludedRoutes($request)` method that is called as the third condition. Mine now looks like this.
public function handle($request, Closure $next)  
{
    if ($this->isReading($request) || $this->excludedRoutes($request) || $this->tokensMatch($request))
    {
        return $this->addCookieToResponse($request, $next($request));
    }

    throw new TokenMismatchException;
}
Notice that I added `excludedRoutes($request)` as the second condition. This is because we don't need to call `$this->tokensMatch($request)` if `excludedRoutes($request)` returns true.

Now we just need to create the tokensMatch($request) method. I wanted an array that I could add my route-exceptions to, so this is what I came up with:

protected function excludedRoutes($request)  
{
    $routes = [
            'some/route/path',
            'users/non-protected-route'
    ];

    foreach($routes as $route)
        if ($request->is($route))
            return true;

        return false;
}

This way, if I ever need to exclude a route from CSRF, I can just add it to the array here.

Telling Laravel about our new middleware class

Looks good! We've got one last step before we're done though. We need to tell Laravel to use our new VerifyCsrfToken class in place of the included one. This is done in the App/Http/Kernal.php file. Go ahead and make the change to:

'App\Http\Middleware\VerifyCsrfToken'

And that's it you're done!

Closing thoughts

There are a few things to keep in mind. One, you need to be very careful anytime you do disable CSRF protection, and whether or not you actually have to disable it. Many times there is a way to include the CSRF token in the request, and if it's possible, you should probably do it. Always be careful and make sure you do have extra protections if this is what you are doing.

This is by no means the only way, nor can I imagine it being the best way, to accomplish this. If you see ways to improve this, or if you have a cleaner way of ignoring CSRF for specific routes, I'm always open to better ways of doing things and I'd love to hear about it.