Let me repeat this part of question that answers here are ignoring:
Can it be done in a few lines of code, without the need to pull in a third party lib?
Reading Cookies
Cookies are read from requests with the Cookie
header. They only include a name
and value
. Because of the way paths work, multiple cookies of the same name can be sent. In NodeJS, all Cookies in as one string as they are sent in the Cookie
header. You split them with ;
. Once you have a cookie, everything to the left of the equals (if present) is the name
, and everything after is the value
. Some browsers will accept a cookie with no equal sign and presume the name blank. Whitespaces do not count as part of the cookie. Values can also be wrapped in double quotes ("
). Values can also contain =
. For example, formula=5+3=8
is a valid cookie.
/**
* @param {string} [cookieString='']
* @return {[string,string][]} String Tuple
*/
function getEntriesFromCookie(cookieString = '') {
return cookieString.split(';').map((pair) => {
const indexOfEquals = pair.indexOf('=');
let name;
let value;
if (indexOfEquals === -1) {
name = '';
value = pair.trim();
} else {
name = pair.substr(0, indexOfEquals).trim();
value = pair.substr(indexOfEquals + 1).trim();
}
const firstQuote = value.indexOf('"');
const lastQuote = value.lastIndexOf('"');
if (firstQuote !== -1 && lastQuote !== -1) {
value = value.substring(firstQuote + 1, lastQuote);
}
return [name, value];
});
}
const cookieEntries = getEntriesFromCookie(request.headers.Cookie);
const object = Object.fromEntries(cookieEntries.slice().reverse());
If you're not expecting duplicated names, then you can convert to an object which makes things easier. Then you can access like object.myCookieName
to get the value. If you are expecting duplicates, then you want to do iterate through cookieEntries
. Browsers feed cookies in descending priority, so reversing ensures the highest priority cookie appears in the object. (The .slice()
is to avoid mutation of the array.)
Settings Cookies
"Writing" cookies is done by using the Set-Cookie
header in your response. The response.headers['Set-Cookie']
object is actually an array, so you'll be pushing to it. It accepts a string but has more values than just name
and value
. The hardest part is writing the string, but this can be done in one line.
/**
* @param {Object} options
* @param {string} [options.name='']
* @param {string} [options.value='']
* @param {Date} [options.expires]
* @param {number} [options.maxAge]
* @param {string} [options.domain]
* @param {string} [options.path]
* @param {boolean} [options.secure]
* @param {boolean} [options.httpOnly]
* @param {'Strict'|'Lax'|'None'} [options.sameSite]
* @return {string}
*/
function createSetCookie(options) {
return (`${options.name || ''}=${options.value || ''}`)
+ (options.expires != null ? `; Expires=${options.expires.toUTCString()}` : '')
+ (options.maxAge != null ? `; Max-Age=${options.maxAge}` : '')
+ (options.domain != null ? `; Domain=${options.domain}` : '')
+ (options.path != null ? `; Path=${options.path}` : '')
+ (options.secure ? '; Secure' : '')
+ (options.httpOnly ? '; HttpOnly' : '')
+ (options.sameSite != null ? `; SameSite=${options.sameSite}` : '');
}
const newCookie = createSetCookie({
name: 'cookieName',
value: 'cookieValue',
path:'/',
});
response.headers['Set-Cookie'].push(newCookie);
Remember you can set multiple cookies, because you can actually set multiple Set-Cookie
headers in your request. That's why it's an array.
Note on external libraries:
If you decide to use the express
, cookie-parser
, or cookie
, note they have defaults that are non-standard. Cookies parsed are always URI Decoded (percent-decoded). That means if you use a name or value that has any of the following characters: !#$%&'()*+/:<=>?@[]^`{|}
they will be handled differently with those libraries. If you're setting cookies, they are encoded with %{HEX}
. And if you're reading a cookie you have to decode them.
For example, while [email protected]
is a valid cookie, these libraries will encode it as email=name%40domain.com
. Decoding can exhibit issues if you are using the %
in your cookie. It'll get mangled. For example, your cookie that was: secretagentlevel=50%007and50%006
becomes secretagentlevel=507and506
. That's an edge case, but something to note if switching libraries.
Also, on these libraries, cookies are set with a default path=/
which means they are sent on every url request to the host.
If you want to encode or decode these values yourself, you can use encodeURIComponent
or decodeURIComponent
, respectively.
References:
Additional information:
=
) sign as in one of Facebook's cookies likefbm_1234123412341234=base_domain=.domain.com
. – Dodge