Alexander Garcia
Parsing Cookie strings and transforming them into usable JavaScript because backend engineers were busy
Read time is about 11 minutes
Alexander Garcia is an effective JavaScript Engineer who crafts stunning web experiences.
Alexander Garcia is a meticulous Web Architect who creates scalable, maintainable web solutions.
Alexander Garcia is a passionate Software Consultant who develops extendable, fault-tolerant code.
Alexander Garcia is a detail-oriented Web Developer who builds user-friendly websites.
Alexander Garcia is a passionate Lead Software Engineer who builds user-friendly experiences.
Alexander Garcia is a trailblazing UI Engineer who develops pixel-perfect code and design.
If you've ever worked on a frontend that consumes cookies set by a backend you don't control, you know the pain. The backend team serializes data in whatever format is natural for their language, and you're left parsing it on the client side with JavaScript. This is especially common in OAuth session management where token metadata may be stored in cookies.
At VA.gov, our authentication system set cookies containing access token and refresh token expiration dates. The frontend needed those dates to manage session lifecycle — things like knowing when to trigger a token refresh, displaying session timeout warnings, and determining if the user's session had expired. The problem? The expiration dates were stored as a Ruby hash, URI-encoded, and crammed into a single cookie string.
We were on a tight deadline and the backend didn't have the resources — as a result the token service wasn't set up to serialize the cookie values as JSON, and refactoring it would have meant blocking the entire OAuth launch. So rather than wait, I wrote a parser on the frontend to transform the raw cookie string into usable JavaScript Date objects. Sometimes you don't get to pick the ideal solution — you pick the one that ships. This is a pattern you'll encounter anytime your frontend and backend speak different languages — literally.
Our backend was built using Ruby, and the cookies it set had two issues:
info_token) contained what we needed// document.cookie "FLIPPER_ID=flipper_on; token_info=%7B%3Aaccess_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A11%3A58.265440745+UTC+%2B00%3A00%2C+%3Arefresh_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A36%3A58.147084176+UTC+%2B00%3A00%7D";
Before writing any code I defined what the end result needed to look like:
Date object (not a string) so I could do comparisons like if (Date.now() > accessTokenExpiration)The goal was to go from that unreadable encoded string to something like:
{ access_token_expiration: Fri Jun 17 2022 16:11:58 GMT+0000, refresh_token_expiration: Fri Jun 17 2022 16:36:58 GMT+0000 }
const oAuthCookieObject = document.cookie // Creates an array of each cookie .split(";") // Maps the cookies to <key>=<value> pairs .map((cookie) => cookie.split("=")) /* Reduces it down to a single object of our access token and refresh tokens by checking if our cookieKey includes the 'info_token' value we are looking for */ .reduce( (_, [cookieKey, cookieValue]) => ({ ...(cookieKey.includes("info_token") && { ...formatOurCookie(decodeURIComponent(cookieValue)), }), }), {}, );
document.cookie// original string "FLIPPER_ID=flipper_on; info_token="[ // after .split ("FLIPPER_ID=flipper_on", "info_token=%7B%3Aaccess_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A11%3A58.265440745+UTC+%2B00%3A00%2C+%3Arefresh_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A36%3A58.147084176+UTC+%2B00%3A00%7D") ];
// original array after .split [ 'FLIPPER_ID=flipper_on', 'info_token=%7B%3Aaccess_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A11%3A58.265440745+UTC+%2B00%3A00%2C+%3Arefresh_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A36%3A58.147084176+UTC+%2B00%3A00%7D' ] // after we use .map [ ['FLIPPER_ID', 'flipper_on'] ['info_token', ['%7B%3Aaccess_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A11%3A58.265440745+UTC+%2B00%3A00%2C+%3Arefresh_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A36%3A58.147084176+UTC+%2B00%3A00%7D'] ]
// String before decodeURIComponent is called const nonDecoded = "%7B%3Aaccess_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A11%3A58.265440745+UTC+%2B00%3A00%2C+%3Arefresh_token_expiration%3D%3EFri%2C+17+Jun+2022+16%3A36%3A58.147084176+UTC+%2B00%3A00%7D"; // String after decodedURIComponent is called const decoded = "{:access_token_expiration=>Fri,+17+Jun+2022+16:11:58.265440745+UTC++00:00,+:refresh_token_expiration=>Fri,+17+Jun+2022+16:36:58.147084176+UTC++00:00}";
formatOurCookie functionfunction formatOurCookie(unformattedCookieString) { return ( unformattedCookieString // Creates an array by splitting on ',+:' to get the access token and refresh token .split(",+:") .reduce((obj, cookieVal) => { // Destructure the key|value pair of the token's name and its expiration date and uses Regex to remove {: and } const [key, val] = cookieVal.replace(/{:|}/g, "").split("=>"); // Update the value by replacing the '+' with spaces and removing the UTC timezone ending const formattedValue = val .replaceAll("++00:00", "") .replaceAll("+", " "); // Return's the accumulator and the key|value pair with a usable JavaScript Date object return { ...obj, [key]: new Date(formattedValue), }; }, {}) ); }
unformattedCookieString parameter which will be a decodeURIComponent string and use the split method on ',+:' to get the access_token_expiration and the refresh_token_expiration into an array// original string "{:access_token_expiration=>Fri,+17+Jun+2022+16:11:58.265440745+UTC++00:00,+:refresh_token_expiration=>Fri,+17+Jun+2022+16:36:58.147084176+UTC++00:00}"[ // array split on the `',+:'` ("{:access_token_expiration=>Fri,+17+Jun+2022+16:11:58.265440745+UTC++00:00", "refresh_token_expiration=>Fri,+17+Jun+2022+16:36:58.147084176+UTC++00:00}") ];
Use the .reduce method to loop through the split array with the goal being to reduce it into a single object.
We want to destructure the key|value pairs by
a. First removing all instances of :{ and } from the string.
// original (removes `:{`) "{:access_token_expiration=>Fri,+17+Jun+2022+16:11:58.265440745+UTC++00:00"; // after removes `:{` "access_token_expiration=>Fri,+17+Jun+2022+16:11:58.265440745+UTC++00:00"; // after removes `}` "refresh_token_expiration=>Fri,+17+Jun+2022+16:36:58.147084176+UTC++00:00";
b. Then by splitting the string on the => using the .split method
// original "access_token_expiration=>Fri,+17+Jun+2022+16:11:58.265440745+UTC++00:00"[ // transformed ("access_token_expiration", "Fri,+17+Jun+2022+16:11:58.265440745+UTC++00:00") ];
c. Format the key's value into a usable format by replacing the + with a single space and removing the ++00:00
// original "Fri,+17+Jun+2022+16:11:58.265440745+UTC++00:00"; // formatted "Fri, 17 Jun 2022 16:11:58.265440745 UTC";
const oAuthCookieObject = document.cookie .split(";") .map((cookie) => cookie.split("=")) .reduce( (_, [cookieKey, cookieValue]) => ({ ...(cookieKey.includes("info_token") && { ...formatOurCookie(decodeURIComponent(cookieValue)), }), }), {}, ); function formatOurCookie(unformattedCookieString) { return unformattedCookieString.split(",+:").reduce((obj, cookieVal) => { const [key, val] = cookieVal.replace(/{:|}/g, "").split("=>"); const formattedValue = val.replaceAll("++00:00", "").replaceAll("+", " "); return { ...obj, [key]: new Date(formattedValue), }; }, {}); }
Fair question. Libraries like js-cookie are great for reading and writing simple key-value cookies. But they don't solve this problem. The challenge here wasn't getting the cookie — document.cookie handles that. The challenge was parsing the value which was a URI-encoded Ruby hash, not JSON. No cookie library is going to know how to deserialize {:access_token_expiration=>Fri,+17+Jun+2022...} into a JavaScript Date. That's custom parsing logic no matter what.
A few things I'd do differently if I were writing this today:
The cleanest solution is always to have the backend serialize cookie values as JSON. A Ruby hash stringified as :key=>value is a Ruby implementation detail that shouldn't leak to the client. If the backend had set the cookie as JSON.generate, the frontend would have just called JSON.parse() and been done.
Unix timestamps (1655482318) are language-agnostic and timezone-safe. Parsing human-readable date strings like "Fri, 17 Jun 2022 16:11:58" with new Date() works but depends on the browser's date parser accepting that exact format. Timestamps eliminate that ambiguity entirely.
The code above assumes the cookie exists and is formatted correctly. In production at VA.gov, we wrapped this in a try/catch and fell back to a logged-out state if parsing failed. Cookie formats can change without warning when the backend deploys — defensive parsing saves you from a blank screen.
That said, sometimes you don't control the backend and you can't wait for changes. In those situations, frontend string parsing like this is the right tool. The broader skill here — chaining split, map, reduce, and regex to transform messy data into clean structures — applies far beyond cookies. I've used the same pattern to parse CSV exports, log files, and URL query strings that didn't follow standard conventions.
The next time your backend hands you a cookie that looks like a serialized object from another language, don't panic. Break the problem down step by step: split the string, decode it, extract the pieces you need, and coerce them into native JavaScript types. It's not glamorous, but it's the kind of practical problem-solving that keeps production applications running.