Managing the size of the resulting cookie was crucial since it might contain multiple JWTs, user account data, and potentially data related to several related business accounts.
To address this, we employed various techniques:
- We mapped JSON data structures to use shorter keys rather than the normal human-readable identifiers.
- The raw cookie string was then compressed using ZLib::deflate to reduce its size.
- When the cookie size approached the de facto 4KB cookie size limit, our solution would recursively split the value into chunks.
- Finally, we leveraged the Rails ActiveDispatch::Cookies module to ensured that cookie values were strongly encrypted using a tamper-evident algorithm before being sent to the client.
Upon receiving a request back from the client, the process was reversed: Rails decrypted the data, and our wrapper would reassemble the chunk, decompress the content, and reinflate JSON keys. This resulted in the remainder of the application being able to use a relatively normal design, having access to a comprehensive "User State" which included the data needed for internal API communications (JWT tokens), UI element permission checks, and user-related data for HTML rendering.
Another unexpected issue arose as part of this work related to the large cookie payloads: our standard application stack uses the Nginx web server in front of the Rails engine for HTTP request termination and serving static content without incurring the overhead of the entire web app stack. However, following implementation of our large-cookie approach we would sometimes see request or response failures with only unclear error messages in the Nginx log.
After investigating we discovered that the default size of the total size of headers in incoming requests and outgoing responses were limited to 4KB in the defalt Nginx configuration. To get around this we added the following config directives:
# allow large response headers
proxy_buffers 8 16k;
proxy_buffer_size 16k;
proxy_busy_buffers_size 16k;
# allow large request headers
large_client_header_buffers 8 16k;
http2_max_field_size 16k;
http2_max_header_size 16k;