Decrypting Laravel’s session cookie with JavaScript and Cloudflare
Mixing traditional backend APIs with serverless is a great way to reduce the load on your computing instances.
Our backend here at Tixel is written in PHP and uses Laravel framework. Now imagine you have a front-end (maybe a SPA) that wants to load user details (eg. the standard /api/me endpoint) on every page so that the web interface has user’s avatar, name and so on wherever it’s needed (typically top right corner, right?).
These details change really really rarely — most users would fill their details once upon sign up and never touch them again. Front-end, on the other hand, is going to ask this again and again — 50, 100 times a day for a single user. It’s a perfect case for some edge point caching!
Serverless @ the edge
Amazon has Lambda@Edge and Cloudflare has Worker functions. Both services allow you to run JavaScript code on hundreds of their servers, and your users will be typically routed to the closest — the “edge” server.
We currently use Cloudflare CDN so this example will be tied to Cloudflare Worker runtime but you can implement the same functionality easily on Cloudfront.
Session cookie
The first time you visit a page on a Laravel-powered website, typically the system will create a new session for you (as long as the page you are hitting has a session middleware attached to it). This session will have a unique ID and this ID won’t change often. Session ID is then passed as a web “cookie” to the browser so that starting from the next browser request we can recognise the user.
For security purposes, this cookie is encrypted (currently AES-CBC algorithm) and the encrypted version will look different every time it’s encrypted. In other words, session 123 will first appear as AAA in the cookie, then as BBB, then as something else.
So we can’t rely on the encrypted version of the session ID in our serverless function code–because it changes every time and we want to be able to cache based on this ID.
If we find a way to cache user details based on the actual session ID and we force Laravel to re-generate session every time user signs out or changes personal details — we would be able to safely cache user details on the edge server. So if user hits /api/me once, it might go to Laravel, but the following 100 requests will be served directly by Cloudfront or Cloudflare, with minimum latency and no load impact on our own computing infrastructure.
Decrypting the Laravel cookie
In our serverless function we receive an incoming request from the browser with all headers and what not. We can extract the cookie like this:
let cookies = request.headers.get('cookie')
let match = cookies.match(/laravel_session=([a-zA-Z0–9%=]+)/)
Now we have to decode it (first URL decode, the Base64 decode), then parse it because it’s a JSON object:
const encrypted = decodeURIComponent(matches[1])
const encryptedParsed = JSON.parse(atob(encrypted))
const keyDecoded = atob(keyEncoded)
Luckily, Cloudflare worker environment has Web Crypto API that supports AES-CBC, so we can go ahead and import our key (prepare your APP_KEY):
const key = await crypto.subtle.importKey("raw", string2buffer(keyDecoded), "AES-CBC", true, ["encrypt", "decrypt"]);const decrypted = await crypto.subtle.decrypt({ name: "AES-CBC", iv: string2buffer(atob(encryptedParsed.iv)) }, key, string2buffer(atob(encryptedParsed.value)));
Voila! Now we have an actual session ID that we can use as a caching key. We can store user details in Worker KV (key-value) store and serve directly from there. Rarely written, often read — exactly the recommended use for Cloudflare serverless functions!
Full code on GitHub:
Epilogue
We are just scratching the surface here. There are definitely more exciting integrations possible between edge functions and traditional backends such as Laravel. Eg. you could use JWT tokens and you could actually store them in Cloudflare Worker KV, then you would be able to do authentication at the edge. This would allow for much more efficient caching — data that is the same for multiple users could be cached correctly since you already know whether a user is authenticated or not before request reaches backend.