DaveBagler's avatar

Sanity check: XSRF-TOKEN httponly

I'm working on an application that requires that all of my app's cookies be both secure and httponly. For the session cookie that's easy enough but for the xsrf-token it's more difficult. I've updated my VerifyCsrfToken middleware by overriding base classe's addCookieToResponse method and tokensMatch method.

    protected function addCookieToResponse($request, $response)
    {
        $response->headers->setCookie(
            new Cookie(
                'XSRF-TOKEN',
                $request->session()->token(),
                time() + 60 * 120,
                '/',
                null,
                true, // Set this to true for secure.
                true // Set this to true for httponly.
            )
        );
        return $response;
    }

The cookie is being set properly but of course I can't access it in AJAX calls (I'm using AngularJS's $http service in my app.) However the the required information does get included by XHR in the cookie HTTP header. So I'm parsing that string in the request to get the xsrf-token value:

   protected function tokensMatch($request)
    {
        $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

        if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
            $token = $this->encrypter->decrypt($header);
        }

        // Here's the section I've added. Step 1: Grab the cookie string from the header.
        if (! $token && $header = $request->header('cookie')) {
            $matchArray = [];
            // Step 2: grab the value of the xsrf-token from the cookie string.
            if (
                preg_match("/XSRF-TOKEN=(.*);/", $header, $matchArray) !== false
                && isset($matchArray[1])
            ) {
                // Step 3: undo the url encoding on the string and then decrypt it.
                $token = $this->encrypter->decrypt(urldecode($matchArray[1]));
            }
        }
        // End of section I've added.

        return StringUtils::equals($request->session()->token(), $token);
    }

From a functional perspective this seems to be working. The XSRF-TOKEN cookie is both httponly and secure, it is getting decrypted accurately and it does match up with the token stored for the session on the server. I'm wondering what people think about using the cookie string in the http header to grab this value. Is there a reason why I shouldn't be using that value? Is there a better way (keeping in mind httponly is a requirement)? Or is this a decent approach?

0 likes
5 replies
bobbybouwmann's avatar

So my first question would be: "Why http only? Why not support https?"

usman's avatar

@bobbybouwmann, please correct me if I am wrong, the httponly and https are two different things meaning that we still need to use the httponly flag on cookies if we want them to be invisible to the Javascripts even if the cookies are secured i.e secure flag is set.

@DaveBagler The above code (your custom section) is not providing any protection against the CSRF attack. An attacker targeting your website can lure a victim on a malicious page into clicking a link and submit a form on your domain (of-cource this requires a session in the victims browser to be present). Since the cookies are automatically submitted with a request the server will never know if the request is coming from a valid user or it is a rogue request.

The whole point of CSRF protection pattern that laravel uses is to manually/explicitly copy the csrf cookie value into a custom HEADER and post it along with the request. Only this way server can guarantee the validity of the request since a javascript on another page cannot read the cookie value (due to the same origin policy) and include it in a custom header.

1 like
DaveBagler's avatar

@bobbybouwmann, httponly just means that a client side script can't access the cookie it doesn't impact http vs https.

@usman, thanks for the detailed response. So in that case would it be correct to say that to use the CSRF protection pattern that laravel uses I cannot make the XSRF-TOKEN cookie httponly?

usman's avatar
usman
Best Answer
Level 27

@DaveBagler yes, making this cookie httponly is pointless and has no benefit. Laravel adds this cookie as a convenience for frameworks like AngularJS that can read and automatically set the X-XSRF-TOKEN header based on the value of this cookie.

You can remove this cookie from the response and use a meta tag (this will require you to add the header value manually using a request interceptor or in run block) or a hidden form field to pass the csrf token back to the server.

Methods are explained here: https://laravel.com/docs/5.2/routing#csrf-protection

Usman.

DaveBagler's avatar

Thanks @usman. I think I'll push back on the requirement that the cookie must be httponly. Makes more sense to me to not mark it as httponly but mark keep it marked as secure.

Please or to participate in this conversation.