198

I want to be able to set a single cookie, and read that single cookie with each request made to the nodejs server instance. Can it be done in a few lines of code, without the need to pull in a third party lib?

var http = require('http');

http.createServer(function (request, response) {
  response.writeHead(200, {'Content-Type': 'text/plain'});
  response.end('Hello World\n');
}).listen(8124);

console.log('Server running at http://127.0.0.1:8124/');

Just trying to take the above code directly from nodejs.org, and work a cookie into it.

Antoni
  • 356
  • 5
  • 17
Corey Hart
  • 10,316
  • 9
  • 41
  • 47

17 Answers17

238

There is no quick function access to getting/setting cookies, so I came up with the following hack:

const http = require('http');

function parseCookies (request) {
    const list = {};
    const cookieHeader = request.headers?.cookie;
    if (!cookieHeader) return list;

    cookieHeader.split(`;`).forEach(function(cookie) {
        let [ name, ...rest] = cookie.split(`=`);
        name = name?.trim();
        if (!name) return;
        const value = rest.join(`=`).trim();
        if (!value) return;
        list[name] = decodeURIComponent(value);
    });

    return list;
}

const server = http.createServer(function (request, response) {
    // To Read a Cookie
    const cookies = parseCookies(request);

    // To Write a Cookie
    response.writeHead(200, {
        "Set-Cookie": `mycookie=test`,
        "Content-Type": `text/plain`
    });

    response.end(`Hello World\n`);
}).listen(8124);

const {address, port} = server.address();
console.log(`Server running at http://${address}:${port}`);

This will store all cookies into the cookies object, and you need to set cookies when you write the head.

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
Corey Hart
  • 10,316
  • 9
  • 41
  • 47
  • 11
    The above code will work incorrectly if the value of a cookie contains an equal (`=`) sign as in one of Facebook's cookies like `fbm_1234123412341234=base_domain=.domain.com`. – esengineer Oct 03 '12 at 09:31
  • 3
    don't cookie values have to be URL encoded / percent encoded? = in the doockie value would be invalid in that case, right? – les2 Mar 13 '13 at 19:52
  • 2
    In that case split by `;` then by the first instance of `=`. Left is key, right is value. – iLoch Jun 03 '13 at 19:33
  • 4
    I ran into the same issue as @Eye, instead of going the route of @iLoch I switched `parts[0]` to `parts.shift()` and `parts[1]` to `parts.join('=')` – aron.duby Aug 10 '13 at 14:23
  • 3
    The code given had serious bugs and people cuntinued to copy it. See my last update – Dan Oct 24 '13 at 17:19
  • 1
    @NicolasZ, no, unfortunately the issues noticed by Eye et. all are still there. I found replacing `list[parts.shift().trim()] = decodeURI(parts.join('='));` with `list[parts[0].trim()] = decodeURI(parts.slice(1).join('='))` as per aron. duby's suggestion to work for me. – user445786 Aug 31 '16 at 17:34
  • @user445786, the changes you mention don't actually change anything that I can see. The output should be identical. – Arlen Beiler Jan 26 '19 at 03:17
  • Unless there is a question of which side of the equals sign get's evaluated first! In other words, does `parts.shift().trim()` really get evaluated before `parts.join('=')`? – Arlen Beiler Jan 26 '19 at 03:19
  • The original problem mention by aron.duby is indeed fixed. And in Chrome dev tools, the left side is evaluated first. So everything should be good. – Arlen Beiler Jan 26 '19 at 03:23
  • Cookie don't have to be URI encoded. The `cookie` npm library just does that by default (which is the express middleware `cookie-parser` depends on). – ShortFuse Jun 02 '20 at 17:32
  • `parts.shift()` returns undefined if parts is an empty array. This will cause a type error. – Charming Robot Jul 13 '20 at 14:27
  • I found 1 vulnerability. decodeURI throws an exception with improper URL encoding. (eg cookie header set to `test=%MKU` → CRASH) –  Feb 20 '21 at 14:16
  • That's not so much a vulnerability as it is expected behaviour, though: if you're parsing any user-side data (not just body content, but also headers, including cookies) always do so in a try/catch (whether you use this code or any other code). – Mike 'Pomax' Kamermans Dec 31 '21 at 18:45
130

If you're using the express library, as many node.js developers do, there is an easier way. Check the Express.js documentation page for more information.

The parsing example above works but express gives you a nice function to take care of that:

app.use(express.cookieParser());

To set a cookie:

res.cookie('cookiename', 'cookievalue', { maxAge: 900000, httpOnly: true });

To clear the cookie:

res.clearCookie('cookiename');
RevNoah
  • 2,344
  • 3
  • 20
  • 28
  • 18
    The cookie library is actually from the underlying library connect; you don't need to take all of express to get cookie helper. – Ajax Sep 03 '12 at 16:40
  • 1
    actually the cookie library is not part of connect (which can't set cookie) – framp Nov 01 '12 at 01:58
  • 12
    `cookie-parser` is no longer part of express and/or connect, but is available as middleware: https://github.com/expressjs/cookie-parser – Koen. Apr 14 '14 at 22:21
  • 7
    How does one "get" a cookie in this example? For completeness and answering the question. – hubatish Jul 16 '15 at 17:48
  • Agreed. I looked at the documentation and it is completely different now. It's hard to provide accurate advice on this topic now, sorry everyone. – RevNoah Jul 21 '15 at 00:13
  • 2
    Downvoting due to needing to install express just to use cookies – Steven Jun 06 '19 at 23:46
  • What if you only need to save cookies, but dont care about its content? – Matej J Dec 20 '19 at 21:08
46

RevNoah had the best answer with the suggestion of using Express's cookie parser. But, that answer is now 3 years old and is out of date.

Using Express, you can read a cookie as follows

var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
app.get('/myapi', function(req, resp) {
   console.log(req.cookies['Your-Cookie-Name-Here']);
})

And update your package.json with the following, substituting the appropriate relatively latest versions.

"dependencies": {
    "express": "4.12.3",
    "cookie-parser": "1.4.0"
  },

More operations like setting and parsing cookies are described here and here

G. I. Joe
  • 1,585
  • 17
  • 21
Kirby
  • 15,127
  • 10
  • 89
  • 104
15

As an enhancement to @Corey Hart's answer, I've rewritten the parseCookies() using:

Here's the working example:

let http = require('http');

function parseCookies(str) {
  let rx = /([^;=\s]*)=([^;]*)/g;
  let obj = { };
  for ( let m ; m = rx.exec(str) ; )
    obj[ m[1] ] = decodeURIComponent( m[2] );
  return obj;
}

function stringifyCookies(cookies) {
  return Object.entries( cookies )
    .map( ([k,v]) => k + '=' + encodeURIComponent(v) )
    .join( '; ');
}

http.createServer(function ( request, response ) {
  let cookies = parseCookies( request.headers.cookie );
  console.log( 'Input cookies: ', cookies );
  cookies.search = 'google';
  if ( cookies.counter )
    cookies.counter++;
  else
    cookies.counter = 1;
  console.log( 'Output cookies: ', cookies );
  response.writeHead( 200, {
    'Set-Cookie': stringifyCookies(cookies),
    'Content-Type': 'text/plain'
  } );
  response.end('Hello World\n');
} ).listen(1234);

I also note that the OP uses the http module. If the OP was using restify, he can make use of restify-cookies:

var CookieParser = require('restify-cookies');
var Restify = require('restify');
var server = Restify.createServer();
server.use(CookieParser.parse);
server.get('/', function(req, res, next){
  var cookies = req.cookies; // Gets read-only cookies from the request
  res.setCookie('my-new-cookie', 'Hi There'); // Adds a new cookie to the response
  res.send(JSON.stringify(cookies));
});
server.listen(8080);
Stephen Quan
  • 21,481
  • 4
  • 88
  • 75
  • 1
    some suggestions now, 3.5 years later, for future viewers. Use `let/const` and `stringifyCookies` can be one lined with a `map` instead of building an array that way. – Ascherer Jan 19 '19 at 16:47
13

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=name@domain.com 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:

Community
  • 1
  • 1
ShortFuse
  • 5,970
  • 3
  • 36
  • 36
  • If we're "clean" modern code, at least also turn that `createSetCookie` into a ```return [`${options.name ?? ``}=${options.value ?? ``}`, options.expires !== undefined && `Expires=${options.expires.toUTCString()}`, options.maxAge != undefined && Max-Age=${options.maxAge}`,...].filter(Boolean).join(`;`);``` (with the relevant indent of course). And note that an unspecified object property resolves to `undefined`, not to `null`. – Mike 'Pomax' Kamermans Dec 31 '21 at 18:32
  • @Mike'Pomax'Kamermans `!= null` checks for both `null` and `undefined` where as `!== undefined` will accept `null`. With your code, if `expires` is null, it'll crash when you call `options.expires.toUTCString()`. The JSDoc notation means you can pass null, with no issues. If not, the parameters would have to be `{!string|undefined}`. Also, there should be a space after semicolons, according to spec. – ShortFuse Jan 04 '22 at 15:53
  • Single string is also faster than iterating arrays twice (`join` and `filter`), though our opinions may differ on legibility. The whole thing as a single String Template is probably even faster, but then I think it gets too unreadable. – ShortFuse Jan 04 '22 at 16:01
  • Oh, wait, you used the coercive `!=`. That's kinda bad form, but sure, that does indeed do that. – Mike 'Pomax' Kamermans Jan 04 '22 at 17:07
  • 1
    @Mike'Pomax'Kamermans It's not bad form. For example, `eslint` does not consider this bad. Most popular implementations use this, including NodeJS itself. There is no coercion because of how `==` works with `null` and `undefined`. You're also not supposed to compare against `undefined` because that's the object in `globalThis` you're comparing against which can be overwritten (Try: `'undefined' in globalThis`). You're supposed to do `typeof object !== 'undefined' && object !== null`. But, using `!= null` is just cleaner. See https://262.ecma-international.org/5.1/#sec-11.9.3 – ShortFuse Jan 04 '22 at 17:56
8

You can use the "cookies" npm module, which has a comprehensive set of features.

Documentation and examples at:
https://github.com/jed/cookies

zah
  • 5,314
  • 1
  • 34
  • 31
  • 1
    Looks like that module is intended for use in http servers. Is there a cookiejar tool for handling cookies in http clients? Basically I aant to tell the http client lib: *if you get any Set-Cookie headers, remember them automatically, and then pass the on subsequent outbound requests as appropriate (when the domain matches).* – Cheeso Sep 09 '12 at 16:02
  • This would be a feature of your http client library of choice. I can suggest [superagent](https://github.com/visionmedia/superagent#persisting-an-agent-with-cookies-ie-sessions) as a good example. – zah Sep 09 '12 at 20:51
  • Gave up trying to get this lib to work in express after couple of hours... use connect instead. – enko Aug 27 '13 at 21:45
7

To get a cookie splitter to work with cookies that have '=' in the cookie values:

var get_cookies = function(request) {
  var cookies = {};
  request.headers && request.headers.cookie.split(';').forEach(function(cookie) {
    var parts = cookie.match(/(.*?)=(.*)$/)
    cookies[ parts[1].trim() ] = (parts[2] || '').trim();
  });
  return cookies;
};

then to get an individual cookie:

get_cookies(request)['my_cookie']
David Beckwith
  • 2,679
  • 1
  • 17
  • 11
6

Cookies are transfered through HTTP-Headers
You'll only have to parse the request-headers and put response-headers.

Tobias P.
  • 4,537
  • 2
  • 29
  • 36
2

Here's a neat copy-n-paste patch for managing cookies in node. I'll do this in CoffeeScript, for the beauty.

http = require 'http'

http.IncomingMessage::getCookie = (name) ->
  cookies = {}
  this.headers.cookie && this.headers.cookie.split(';').forEach (cookie) ->
    parts = cookie.split '='
    cookies[parts[0].trim()] = (parts[1] || '').trim()
    return

  return cookies[name] || null

http.IncomingMessage::getCookies = ->
  cookies = {}
  this.headers.cookie && this.headers.cookie.split(';').forEach (cookie) ->
    parts = cookie.split '='
    cookies[parts[0].trim()] = (parts[1] || '').trim()
    return

  return cookies

http.OutgoingMessage::setCookie = (name, value, exdays, domain, path) ->
  cookies = this.getHeader 'Set-Cookie'
  if typeof cookies isnt 'object'
    cookies = []

  exdate = new Date()
  exdate.setDate(exdate.getDate() + exdays);
  cookieText = name+'='+value+';expires='+exdate.toUTCString()+';'
  if domain
    cookieText += 'domain='+domain+';'
  if path
    cookieText += 'path='+path+';'

  cookies.push cookieText
  this.setHeader 'Set-Cookie', cookies
  return

Now you'll be able to handle cookies just as you'd expect:

server = http.createServer (request, response) ->
  #get individually
  cookieValue = request.getCookie 'testCookie'
  console.log 'testCookie\'s value is '+cookieValue

  #get altogether
  allCookies = request.getCookies()
  console.log allCookies

  #set
  response.setCookie 'newCookie', 'cookieValue', 30

  response.end 'I luvs da cookies';
  return

server.listen 8080
  • 3
    Just copy paste that code in the TRY COFFESCRIPT tab on http://coffeescript.org/. Your answer did help me, and coffeescript is not that hard to read if you know javascript. – Mattijs May 25 '14 at 11:54
1

Using Some ES5/6 Sorcery & RegEx Magic

Here is an option to read the cookies and turn them into an object of Key, Value pairs for client side, could also use it server side.

Note: If there is a = in the value, no worries. If there is an = in the key, trouble in paradise.

More Notes: Some may argue readability so break it down as you like.

I Like Notes: Adding an error handler (try catch) wouldn't hurt.

const iLikeCookies = () => {
    return Object.fromEntries(document.cookie.split('; ').map(v => v.split(/=(.+)/))); 
}

const main = () => {
    // Add Test Cookies
    document.cookie = `name=Cookie Monster;expires=false;domain=localhost`
    document.cookie = `likesCookies=yes=withARandomEquals;expires=false;domain=localhost`;

    // Show the Objects
    console.log(document.cookie)
    console.log('The Object:', iLikeCookies())

    // Get a value from key
    console.log(`Username: ${iLikeCookies().name}`)
    console.log(`Enjoys Cookies: ${iLikeCookies().likesCookies}`)
}

enter image description here

What is going on?

iLikeCookies() will split the cookies by ; (space after ;):

["name=Cookie Monster", "likesCookies=yes=withARandomEquals"]

Then we map that array and split by first occurrence of = using regex capturing parens:

[["name", "Cookie Monster"], ["likesCookies", "yes=withARandomEquals"]]

Then use our friend `Object.fromEntries to make this an object of key, val pairs.

Nooice.

Steve
  • 4,372
  • 26
  • 37
1

You can use cookie lib to parse incoming multiple cookies, so that you won't have to worry about exceptions cases:

var cookies = cookie.parse('foo=bar; equation=E%3Dmc%5E2');
// { foo: 'bar', equation: 'E=mc^2' }

To write a cookie you can do like this:

response.writeHead(200, {
    "Set-Cookie": `mycookie=cookie`,
    "Content-Type": `text/plain`
});
Esset
  • 916
  • 2
  • 15
  • 17
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 16 '23 at 15:38
0
var cookie = 'your_cookie';
var cookie_value;
var i = request.headers.indexOf(cookie+'=');
if (i != -1) {
  var eq = i+cookie.length+1;
  var end = request.headers.indexOf(';', eq);
  cookie_value = request.headers.substring(eq, end == -1 ? undefined : end);
}
Simon
  • 2,067
  • 2
  • 17
  • 30
0

If you don't care what's in the cookie and you just want to use it, try this clean approach using request (a popular node module):

var request = require('request');
var j = request.jar();
var request = request.defaults({jar:j});
request('http://www.google.com', function () {
  request('http://images.google.com', function (error, response, body){
     // this request will will have the cookie which first request received
     // do stuff
  });
});
BlackBeard
  • 10,246
  • 7
  • 52
  • 62
  • Sorry, but this code makes no sense at all to me. Is `jar()` some cutesy method name to get the current list of cookies? What is the point of the 2 invocations of request? This is mostly an advertisement, not an answer. – user1944491 Apr 26 '20 at 18:36
  • Client answers are not accepted. Server answers are accepted. –  Feb 20 '21 at 14:58
0

I wrote this simple function just pass req.headers.cookie and cookie name

const getCookieByName =(cookies,name)=>{
    const arrOfCookies = cookies.split(' ')
    let yourCookie = null

    arrOfCookies.forEach(element => {
        if(element.includes(name)){
            yourCookie = element.replace(name+'=','')
        }
    });
    return yourCookie
}
Ahmed Magdy
  • 1,054
  • 11
  • 16
0

I know that there are many answer to this question already, but here's a function made in native JS.

function parseCookies(cookieHeader) {
    var cookies = {};
    cookieHeader
        .split(";")
        .map(str => str.replace("=", "\u0000")
        .split("\u0000"))
        .forEach(x => cookies[x[0]] = x[1]); 

    return cookies;
}

It starts by taking in the document.cookie string. Every key-value pair is separated by a semicolon (;). Therefore the first step is to divide the string up each key-value pair.

After that, the function replaces the first instance of "=" with a random character that isn't in the rest of the string, for this function I decided to use the NULL character (\u0000). The key-value pair can now be split into just two pieces. The two pieces can now be combined into JSON.

0

Set Cookie

let key = "eyJhbGciOiJ...";
response.setHeader('Set-Cookie', `key=${key}; HttpOnly`);

Get Cookie

let val = request.headers.cookie.split("=")[1];
Ronnie Royston
  • 16,778
  • 6
  • 77
  • 91
-1

First one needs to create cookie (I have wrapped token inside cookie as an example) and then set it in response.To use the cookie in following way install cookieParser

app.use(cookieParser());

The browser will have it saved in its 'Resource' tab and will be used for every request thereafter taking the initial URL as base

var token = student.generateToken('authentication');
        res.cookie('token', token, {
            expires: new Date(Date.now() + 9999999),
            httpOnly: false
        }).status(200).send();

To get cookie from a request on the server side is easy too.You have to extract the cookie from request by calling 'cookie' property of the request object.

var token = req.cookies.token; // Retrieving Token stored in cookies
Ankit kaushik
  • 1,043
  • 10
  • 6