Excluding URI from CSRF Protection not working

Published 1 year ago by adamtomat

According to the docs (5.1) it's possible to exclude URI's easily with the protected $except property, like so:

    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'api/*'
    ];

Here I am trying to exclude the Set-Cookie :XSRF-TOKEN from API requests. However, this isn't working.

So I dug into Illuminate\Foundation\Http\Middleware\VerifyCsrfToken and became really confused. By the looks of it, the exclude will be ignored on any GET request, and therefore you get a cookie set. Here's the handle method:

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

        throw new TokenMismatchException;
    }

isReading() will return true if the request is GET, rendering the results of shouldPassThrough() pointless. There is only 2 outcomes to this method too: 1) you get a cookie set, 2) you get an exception. I'd expect a third outcome of no cookie if the request matches an except item, right?

Also, looking at shouldPassThrough() seems backwards too. It returns true if the request matches an except item:

    /**
     * Determine if the request has a URI that should pass through CSRF verification.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function shouldPassThrough($request)
    {
        foreach ($this->except as $except) {
            if ($request->is($except)) {
                return true;
            }
        }

        return false;
    }

The method docs suggest that if it returns true it should pass through CSRF verification...i.e. get a cookie. But it only returns true if it matches an except item i.e. we don't want a cookie.

To get the behaviour I expect, I had to create my own addCookieToResponse method which only adds a cookie if the request doesn't match any except items;

    protected function addCookieToResponse($request, $response)
    {
        if (!$this->shouldPassThrough($request)) {
            $response = parent::addCookieToResponse($request, $response);
        }

        return $response;
    }

Can somebody either validate this (as a bug?), or explain to me how it is intended to work please? Because what the docs (and code) imply don't make sense to me.

bobbybouwmann

You only need the token for post, put, patch and delete. So I don't see what the issue is here... You still need the cookie to be created here right? Let's say you do a get request to grab a form to create a user. You need that cookie so you can post a _token.

You say you don't need the cookie here, but the next request might need the cookie. So you create the cookie for the next request. Therefore you can check if the token is correct or not.

I couldn't think of a quick example here, but I hope you get the idea. Let me know if you need a better explanation ;)

adamtomat

Thanks for clarifying why the cookie is needed and I get that we need the token for those methods you mentioned. But in the context of an API we're never using the token and end up sending unnecessary bytes down the wire on every request. That's why I would like to remove it. Just for API requests.

Even with $except = ['*'], it still adds the token to the headers for GET requests. I'd expect it to disable all CSRF related stuff for the given uri, including the setting of the Set-Cookie header.

bobbybouwmann

It's just how Laravel works. I need to dive deeper in it to find out how to update that functionality...

Snapey
Snapey
1 year ago (575,575 XP)

Even with $except = ['*'], it still adds the token to the headers for GET requests. I'd expect it to disable all CSRF related stuff for the given uri, including the setting of the Set-Cookie header.

There is no way of knowing that the response from a get will be used or will not used for a form posting.

The $except is for excluding routes from csrf checks, not for determining if the token is sent.

Afaceri
Afaceri
1 year ago (14,920 XP)
public function __construct(Encrypter $encrypter)
{
    $this->encrypter = $encrypter;
    $this->except = ['page/thepage'];
}

Sign In or create a forum account to participate in this discussion.