Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cookie session driver won't save any validation error or flash data

Tags:

I am having a hard time with the cookie session driver in Laravel.

I have a simple form with a validation in place. This is my method for saving this form data:

public function store() {     $this->validate(request(), [         'name'        => 'required',         'title'       => 'required',         'description' => 'required|max:600',         'image'       => 'required|file|mimes:jpeg,png',     ]);      $member = TeamMember::create(request()->all());     $member->addImage(request()->file('image'));      return redirect()->route('backoffice.team-members'); } 

Pretty simple.

The problem is that, when using the cookie session driver, if I save this form with a description that's 1024 characters long, I'll get redirected back but with no flash data and no $errors in the view for the next request to handle.

Example:

enter image description here

This is a POST after using this line:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce gravida eros ut leo commodo luctus. Nulla neque dui, laoreet quis felis in, porta tincidunt felis. Phasellus in lacus et sem condimentum ornare. Praesent vitae nisi tempus, gravida tortor eu, convallis dui. Cras lacinia posuere scelerisque. Vestibulum tincidunt purus id sollicitudin varius. Sed eros urna, mattis nec nunc eu, finibus suscipit ipsum. Aliquam varius faucibus congue. Vivamus convallis imperdiet sem a commodo. Proin cursus feugiat sem a pharetra. Curabitur rhoncus non quam sit amet lacinia. Sed ut nisl id odio faucibus vehicula vel ut erat. Vestibulum ut iaculis magna. Quisque sit amet massa sodales, suscipit nisl eu, dapibus elit. Morbi posuere ligula pretium commodo semper. Nam odio elit, rutrum finibus tortor eget, viverra viverra metus. Proin tincidunt tempor ex pretium rhoncus. Proin egestas erat sed eros congue, mollis gravida magna bibendum. Pellentesque vel bibendum nunc. Orci varius natoque penatibus et magnis dis viverra fusce. 

In the description field. 1024 bytes to be exact.

Instead, if I just fill the field with some more dummy data but nothing too crazy:

enter image description here

If I change the session driver to file:

enter image description here

... it works.

But this does not fix my problem. I do need to use the cookie driver for session as the production website is running in 3 different datacenters to achieve high availability. Using the cookie for the session allows the user to hit any of the 3 servers and still continue with it's request without having to use any sticky session or any central session driver.

Using the database as a driver—which is also in a cluster with HA—is not an option as this is a really high traffic website and that would be a write per request which doesn't sound appealing at all. I would like to prevent that at any cost.

There is anyway this can be solved?

I must say this is the backoffice of the website, but soon the user in the frontend will be able to also write more than 1024 characters in a textarea... so if I just change the driver for the backoffice doesn't help, as we will run into the same for our users.

like image 911
ipalaus Avatar asked Oct 16 '17 03:10

ipalaus


1 Answers

The cookie session driver is not suitable for applications that must store any significant amount of data in the user's session. Browsers typically limit data stored in one cookie to around 4 KB (4096 bytes). As we found, we can easily exhaust this capacity by attempting to store a 1024-character-long string in the session cookie—the "Lorem ipsum..." string in the question contains only ASCII characters, and each ASCII character is represented using 4 bytes, so 1024 × 4 = 4096 bytes.

As we can see, we quickly begin to run out of space when we need to store additional items in one session cookie, such as the serialization metadata for PHP values, or when the data contains UTF-8 characters that consume more than 4 bytes per character. To continue to use cookies to store session data greater than 4 KB, we'd need to write a custom session driver that partitions session data across multiple cookies for each response and then reassembles them on each request. For good reason, no existing packages do this that I'm aware of. This brings me to my next point...

I want to strongly discourage the use of cookies for storing a full session (instead of just a session ID). This approach can expose security vulnerabilities, adds overhead to requests and responses (including requests for static assets on the same domain), risks desynchronizing data (right-click app tab → Duplicate to see what happers), complicates tests and debugging, and causes problems when storing certain kinds of data.

For the distributed application in the question, we have four options:

  1. Store the session data on a central server accessed by each application instance
  2. Store the session state at each site, and replicate it between sites
  3. Use "sticky" sessions to anchor a user to one site after starting a session
  4. Write a non-trivial amount of code to implement a custom session driver that allows the client to pass session state without using cookies, or one that partitions cookies

I think we can reasonably eliminate the fourth option. Unless we have a very good reason to avoid using the first three approaches, and for typical applications we usually don't, we cannot justify the amount of work, complexity, and overhead needed to build a system to transfer session data back and forth across HTTP when the first three options are standard, widely-accepted solutions.

Based on the information in the question, it sounds to me like you already understand the concepts and implications behind the other three options, so I won't elaborate on explanations for each one (but do comment to let me know if I should).

For applications without advanced infrastructure requirements, I recommend the third approach: sticky sessions. These are relatively easy to set up and usually require configuration on the load balancer so that once a client starts a session with an application server, the load balancer routes any subsequent requests to the same server until the session ends. For high-availability, we can combine this approach with the Redis session driver and Redis servers configured for master-slave replication between the datacenters, and, optionally, Redis Sentinel to automate failover. Redis suits session data well and offers better performance than a relational database. If we have multiple application instances at each data center, Redis provides a central location for session data for all instances at one site.

For completeness, and to directly answer the question, here's an overview for the development needed to create a cookie-based session driver that handles session data greater than 4 KB. Again, I recommend one of the other approaches described above:

  • First, we'll need to create a new class that implements PHP's SessionHandlerInterface (check out Laravel's cookie session handler for a starting point—we can probably extend this class).

  • The write() method of this class will need to serialize the session $data and then split the data into chunks smaller than 4 KB (for some browsers, less that 4093 bytes). Be sure to account for multibyte characters. Before splitting the data, we may also want to encrypt it if the session contains sensitive information or if we don't want clever users to mess with the values. Then, the method should add a new cookie for each chunk of the session data. Each cookie will need to contain the sequence in its name, and we can add an additional cookie that contains the number of chunks.

  • The read() method will perform these operations in reverse. First, it will reassemble each chunk from the cookies in the request using the value of the cookie that contains the number of chunks, and then, optionally, decrypt the data if we encrypted it.

  • The destroy() method should clear the contents of each cookie that contains a chunk.

  • Then we'll choose a name for the session driver, such as cookie-extended, and, in a service provider's boot() method, register it as an available driver using :

    Session::extend('cookie-extended', ...); 

    We'll need to instruct the application, in config/session.php or in .env, to use the new driver for user sessions.

If you do decide to go down this path, be sure to test the implementation in every browser you plan to support, as different browsers impose their own limitations on the size and quantity of cookies. Even if we've increased the amount of session data we can store as cookies, we're still limited to the maximum number of cookies a browser will accept per domain.

As a final note, storing sessions in the database may not impact performance as much as you think. It could be worth measuring the actual load created by this simple solution before investing many hours optimizing for a concern that might not be a real issue.

like image 70
Cy Rossignol Avatar answered Oct 16 '22 12:10

Cy Rossignol