DEV Community

Alexander Garcia
Alexander Garcia

Posted on • Updated on

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:

  1. Multiple cookies were stored for the Frontend
  2. 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'
Enter fullscreen mode Exit fullscreen mode

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))
    })
  }), {});
Enter fullscreen mode Exit fullscreen mode

Part 1 Breakdown

1) We create a variable from document.cookie

2) 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']
Enter fullscreen mode Exit fullscreen mode

3) 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']
]
Enter fullscreen mode Exit fullscreen mode

4) 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}'
Enter fullscreen mode Exit fullscreen mode

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)
      }
  }, {})
}

Enter fullscreen mode Exit fullscreen mode

Part 2 of Breakdown of formatOurCookie function

1) Take the 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}'
]
Enter fullscreen mode Exit fullscreen mode

2) Use the .reduce method to loop through the split array with the goal being to reduce it into a single object.

3) 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'
Enter fullscreen mode Exit fullscreen mode

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'
]
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

4) 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)
      }
  }, {})
}
Enter fullscreen mode Exit fullscreen mode

Hopefully some of you found that useful. Cheers! 🎉

Discussion (2)

Collapse
ahmad_butt_faa7e5cc876ea7 profile image
Ahmad

thanks for sharing the code! interesting use case with multiple cookies..any specific reason why if you dont mind me asking?

Collapse
asg5704 profile image
Alexander Garcia Author

Sure. On our project we have multiple cookies being set for OAuth with most of them being httpOnly cookies (meaning they aren't accessible to JavaScript). We decided to store the token's expiration in a cookie available in JavaScript so that we could reduce the number of network requests and only call a silent refresh request when needed.