ezero
ezero

Reputation: 1310

Laravel DecryptException - The payload is invalid

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?

Upvotes: 21

Views: 81557

Answers (7)

Tofandel
Tofandel

Reputation: 3585

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,
    //...

Upvotes: 4

AID
AID

Reputation: 144

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

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

protected $except = [
   'XSRF-TOKEN',
];

Upvotes: 1

rayalois22
rayalois22

Reputation: 161

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

Upvotes: -1

Mrudul Addipalli
Mrudul Addipalli

Reputation: 624

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.

Upvotes: 1

Andy White
Andy White

Reputation: 446

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.

Upvotes: 0

VnoitKumar
VnoitKumar

Reputation: 1426

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

Upvotes: 21

ezero
ezero

Reputation: 1310

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;
            }

Upvotes: 8

Related Questions