17

I am sending an AJAX post request back to my Laravel API and receiving this error messsage:

DecryptException in compiled.php line 13235: The payload is invalid.

I am reading the XSRF-TOKEN from the cookie and sending that along as a request header named X-XSRF-TOKEN.

The site is a completely seperate site from the Laravel API but shares the same session, which is why I am getting the value from a cookie.

The strange thing is, occasionally it works. Any ideas what is causing this?

ezero
  • 1,230
  • 3
  • 12
  • 27
  • Are you sending `XSRF-TOKEN` or `X-CSRF-TOKEN`? – Pankaj Makwana Jun 20 '17 at 11:49
  • I am sending a header name: X-XSRF-TOKEN, the value is taken from the cookie named XSRF-TOKEN – ezero Jun 20 '17 at 11:51
  • Send the `X-CSRF-TOKEN` header. The `XSRF-TOKEN` cookie is not encrypted but laravel expects the `X-XSRF-TOKEN` header to be encrypted. – apokryfos Jun 20 '17 at 11:53
  • Checkout this post that may help you https://stackoverflow.com/questions/42408177/what-is-the-difference-between-x-xsrf-token-and-x-csrf-token – Pankaj Makwana Jun 20 '17 at 11:53
  • @apokryfos - The only thing I have available to use in the cookies is XSRF-TOKEN and it appears to be encrypted, here's an example value: eyJpdiI6Ikd1SFlHaGdLcFwvY0NWR2tRUmU0bk9RPT0iLCJ2YWx1ZSI6IlUwQlFMaStVY3FxV0owdE8xUkV0cDF4ZmFaR21KbDJtTUVyN3ZSRk1FZFwveHoxMWR6OUZ3U0t1UE81MlwvbHNTcmZ0VlJhUnpQejhCblhXMVRqWFV6Umc9PSIsIm1hYyI6ImIyYzJkMzgxMTMxOGFiODg3MTIyYzhmZjE0NmFjZWUzZjg0MTMzYzBlZTkwNGFmMzFjNDY3ZTc5ZGVkYmRjMjkifQ%3D%3D – ezero Jun 20 '17 at 11:57
  • The laravel CSRF token is an encrypted value indeed, but laravel does not need to decrypt it to verify it because it already knows what the encrypted value is meant to be. That's why you should send it in the `X-CSRF-TOKEN` header because then it will just verify it's correct. – apokryfos Jun 20 '17 at 12:01
  • 1
    The documentation states this: Laravel also stores the CSRF token in a XSRF-TOKEN cookie. You can use the cookie value to set the X-XSRF-TOKEN request header. Some JavaScript frameworks, like Angular, do this automatically for you. - This is exactly what I an doing. – ezero Jun 20 '17 at 12:05

7 Answers7

19

If you are sending X-XSRF-TOKEN from JavaScript you can decode it using decodeURIComponent(). It converts %3D to =.

bfontaine
  • 18,169
  • 13
  • 73
  • 107
VnoitKumar
  • 1,350
  • 15
  • 29
8

I found out the cause of the problem. The XSRF-TOKEN cookie value sometimes had a rogue character appended to the end: '%3D' - sometimes there are two of these on the end. No idea how they get there but when they are present, the verification fails.

If you base64_decode the cookie value, you get a json string which has the rogue character: '7' appended to the end so Laravel's decrypt method fails.

I ended up having to write my own CSRF verify function:

$payload = base64_decode($request->header('X-XSRF-TOKEN'));

            //Remove any rogue chars from the end of the json  
            for($i=0; $i<strlen($payload); $i++){
                $lastChar = substr($payload, -1);
                if($lastChar != '}'){
                    $payload = substr($payload, 0, -1);
                } else {
                    break;
                }
            }

            //Needs to be base64 encoded when passed to decrypt
            $payload = base64_encode($payload);

            $headerToken = decrypt($payload);
            $cookieToken = $request->cookie('XSRF-TOKEN');

            //Compare tokens
            if($headerToken == $cookieToken){
                return true;
            } else {
                return false;
            }
ezero
  • 1,230
  • 3
  • 12
  • 27
  • 1
    i'm also getting this weird character attached to the end of my XSRF-TOKEN cookie value. Anyone know what's the cause of it? – Ivan Caceres Aug 13 '17 at 00:04
  • I think it's just a default browser behavior to encode the cookie for url.. The problem is laravel is not urldecoding it beforehand – Tofandel Sep 21 '20 at 16:00
  • As such I would recommend to just add `$_COOKIE = array_map('urldecode', $_COOKIE);` at the top of your `server.php` – Tofandel Sep 21 '20 at 16:07
4

I had the same issue and it was indeed due to the cookie being url encoded and not decoded correctly by laravel

You can solve it by url decoding the cookies before decryption

in app/Http/Middleware/EncryptCookies.php

<?php

namespace App\Http\Middleware;

use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;

class EncryptCookies extends Middleware {
   /**
     * Decrypt the given cookie and return the value.
     *
     * @param  string  $name
     * @param  string|array  $cookie
     * @return string|array
     */
    protected function decryptCookie($name, $cookie)
    {
        return parent::decryptCookie($name, is_array($cookie) ? $cookie : urldecode($cookie));
    }
}

Make sure you are using the correct middleware in app/Http/Kernel.php, you should look for EncryptCookies and replace it with the new class

Eg:

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
    //...
Tofandel
  • 3,006
  • 1
  • 29
  • 48
1

it can also happen because of encryption / decryption error, where plain text from database is been decrypted by the decryption method/function, because you would have added some data directly into the database which needs to be encrypted bot inserted as plaintext , so it can be related to encryption / decryption issue.

Mrudul Addipalli
  • 574
  • 6
  • 10
1

+1 to Tofandel's answer, I removed the encryption of 'XSRF-TOKEN' cookie.

In app/Http/Middleware/EncryptCookies.php add:

protected $except = [
   'XSRF-TOKEN',
];
Danon
  • 2,771
  • 27
  • 37
AID
  • 124
  • 5
0

I had a similar issue but it seems to be related only to Google Chrome. I modified EncryptCookies to dd() whenever it encountered a decrypt exception:

protected function decrypt(Request $request)
{
    foreach ($request->cookies as $key => $c) {
        if ($this->isDisabled($key)) {
            continue;
        }

        try {
            $request->cookies->set($key, $this->decryptCookie($c));
        } catch (DecryptException $e) {
            dd('exception: ', $e, $key, $c, $request); // added by me
            $request->cookies->set($key, null);
        }
    }

    return $request;
}

Strangely, whenever I refresh the page, sometimes the DecryptException is thrown but most of the time the try statement succeeds. When I test in IE and Firefox, the try statement always succeeds. It seems to related to the amount of data in my request headers but the problem is non-deterministic.

Andy White
  • 438
  • 2
  • 11
  • Hi, I'm using Mozilla browser but still facing the same issue any help ?? – Gopi P Feb 06 '20 at 02:23
  • 1
    Unfortunately I never resolved the issue. I had the same problem as ezero mentioned where a rogue character would appear at the end. It seemed related to the amount of data in the request headers. – Andy White Feb 07 '20 at 04:02
  • I can concur chrome seems to urlencode cookies when they go over 2Kb – Tofandel Nov 12 '20 at 12:28
-1

Here is a solution and steps that work for me in Laravel:

  1. Include the CSRF token in your document's header as a meta tag named csrf-token:

<!-- CSRF Token -->
 <meta name="csrf-token" content="{{ csrf_token() }}">
  1. Create a JavaScript function to retrieve the value of the csrf-token meta tag:

function csrf(name="csrf-token"){
    const metas = document.getElementsByTagName('meta');
    for (let i = 0; i < metas.length; i++) {
        if (metas[i].getAttribute('name') === name) {
            return metas[i].getAttribute('content');
        }
    }
    
    return null;
}
  1. Set the X-CSRF-TOKEN header for your request, giving it the value of the csrf() function. An example using the Fetch API:

let params = {headers:{}};
params.headers["Content-Type"] = "application/json; charset=UTF-8";
params.headers["X-CSRF-TOKEN"] = csrf(); // call your csrf() function
params.mode = "same-origin";
params.method = "POST";
let response = await fetch('/your-url', params);
// handle the response object
rayalois22
  • 161
  • 3
  • 7
  • The question clearly states that the front end is completely separate from the Laravel API backend so they cannot use {{ csrf_token() }} – Aaron Jun 09 '20 at 15:19