From Cookie strings into usable JavaScript
Background
Hello all, this will be a slightly in-depth post that I hope helps some developers out there.
We are using OAuth and one of the ways we are using session management on the Front-end is by providing cookies of each of the Access Token and Refresh Token expiration dates.
Assumptions
I assume that the reader is knowledgable on using .split, .map, and .reduce array methods.
The Problem
Our Backend is built using Ruby on Rails and the problem I was facing when the response cookies were set were:
- Multiple cookies were stored for the Frontend
- Both the access token expiration and refresh token expiration are stored as a Ruby hash in an encoded string.
// 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";
Requirements
From here I had a few requirements of my own to ensure the session management work as I expected
- An object with both the access token & refresh token expirations
- Having the expirations be usable JavaScript dates
Part 1 of the Creating Our Cookie Object
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)),
}),
}),
{}
);
Part 1 Breakdown
- We create a variable from
document.cookie
- We split each cookie string
// 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")
];
- We map each cookie to a new array of arrays by splitting on the '='
// 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']
]
- We reduce to a single usable object by destructing the cookie's key|value pair if it matches our 'info_token' and call another function with the value being interpreted as a decodedURIComponent string.
// 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}";
Part 2 of Creating our Cookie Object formatOurCookie
function
function 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),
};
}, {})
);
}
Part 2 of Breakdown of formatOurCookie
function
- Take the
unformattedCookieString
parameter which will be adecodeURIComponent
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";
- Return the accumulator and coerce the above string into a usable JavaScript Date
TL/DR
const oAuthCookieObject = document.cookie
.split(";")
.map((cookie) => cookie.split("="))
.reduce(
(_, [cookieKey, cookieValue]) => ({
...(cookieKey.includes("info_token") && {
...formatOAuthCookie(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),
};
}, {});
}
Hopefully some of you found that useful. Cheers! π
If you enjoyed this article please feel free to connect with me on Dev.to or on LinkedIn