A Javascript one-liner for retrieving cookie values

One useful Javascript convenience function I ran across a while back is the javascript:alert(document.cookie) browser scriptlet. If you type this into the address bar of your browser, it will pop up a list of all of the cookies currently set in this domain (try it!) I use this quite a bit, but one thing that always bothered me was that it showed me all of the cookies when I was usually only interested in the value of a single one, whose key I knew. For instance, if I go over to amazon.com and show all of my cookies, I see:

aws-x-main=74TVFa0BSwhflf0oMuebP1IywvY4uek; __utma=3822618247.014730400.50
1177117.676853367.3513281.5; __utmz=37183305.21457436277.4.6.utmccn=(organic)|
utmcsr=google|utmctr=|utmcmd=organic; aws-ubid-main=371-8330521-4574362; 
aws-session-id-time=5514168847l; aws-session-id=677-3752086-8645371; 
apn-user-id=xbkws5k4-xray-sai4-onop-tjj4ttyw7kee; x-main=bUcjNIertj6ERMXXGO
6b56jIyEBSRIbN; session-token=hy5a5/tPr/6r1ipwglUUF53fHF8l8PDu5J2RIva/SY0sL
tElkhyWSa4BQjS1cgGcsb+IJDvtTuQhOrLMc4fPpXKVrqf4Mxdk1+mcgM4hoGRKbOKhBLrdCQXL
PcrN474N1uC2EnDbf2/55NWdTYPEkQvNtIr5SxdnJ74OpamHKrSluwmUBVbPMV5nxTwS83gavSE
pHtMURWM8+DEVJAYDeH8Uv8taAsMx3RF5FixOfSBm+pTnX7FrjXPHDk8Vul/r4QRiATtUeYviaL
e+TfkXW==; csm-hit=456.92; ubid-main=400-3487403-7234182; session-id-time=4
0034874037l; session-id=778-6702530-6814121;
(All of the above, of course, has been anonymized... thoroughly, I hope...) Supposing that I'm only interested in the value of the session token. This appears in the middle of the display, and I have to "hunt" for it to find it.

It would be nice if Javascript would parse the cookie into an object-like format for me so that I could say, for example:

javascript:alert( document.cookie[ "session-token" ] )
But, unfortunately, Javascript doesn't work that way - although you can set a cookie value that way, you can't retrieve one that way. I figured a way to work around this using regular expression syntax:
javascript:alert( document.cookie.match( /(; )?session-token=([^;]*);?/ )[ 2 ] )
But that always sort of bugged me because the string to match was in the middle of the scriptlet; if I had it copied somewhere for a quick cut-and-paste, I had to select the middle part of the scriptlet and replace the name of the cookie I was after there. It also errors out silently if the cookie isn't present, which is sort of annoying.

With the relatively recent standardization of map and reduce in Javascript 5, I was able to put together an arguably better implementation. The solution I came up with, for the impatient, is:

javascript:alert( document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } ).reduce( 
function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ "session-token" ] );

Ok, so it's not exactly succinct, but it is something that you can type into a single line and get the browser to quickly report the value of a single cookie. Although by the time you type all this in, I imagine it's less time and effort to just pull up the developer console, this way is more fun (right?). And who knows? You may be working in an ancient browser that doesn't have a javascript console.

This works, but it's worth picking apart to see exactly why — I think it makes an interesting exposition of functional programming techniques in Javascript (and also highlights some of the ways Javascript could do a better job managing the intersection of object-oriented and functional programming).

First, of course, I split the cookie on the ';' separators to get an array containing:

javascript:alert( document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } ).reduce( 
function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ "session-token" ] );
a[ 0 ] = aws-x-main=74TVFa0BSwhflf0oMuebP1IywvY4uek
a[ 1 ] = __utma=3822618247.014730400.501177117.676853367.3513281.5
a[ 2 ] = __utmz=37183305.21457436277.4.6.utmccn=(organic)|utmcsr=google|utmctr=|utmcmd=organic
a[ 3 ] = aws-ubid-main=371-8330521-4574362
a[ 4 ] = aws-session-id-time=5514168847l
a[ 5 ] = aws-session-id=677-3752086-8645371
a[ 6 ] = apn-user-id=xbkws5k4-xray-sai4-onop-tjj4ttyw7kee
a[ 7 ] = x-main=bUcjNIertj6ERMXXGO6b56jIyEBSRIbN
a[ 8 ] = session-token=hy5a5/tPr/6r1ipwglUUF53fHF8l8PDu5J2RIva/SY0sLtElkhyWS
a4BQjS1cgGcsb+IJDvtTuQhOrLMc4fPpXKVrqf4Mxdk1+mcgM4hoGRKbOKhBLrdCQXLPcrN474N1
uC2EnDbf2/55NWdTYPEkQvNtIr5SxdnJ74OpamHKrSluwmUBVbPMV5nxTwS83gavSEpHtMURWM8+
DEVJAYDeH8Uv8taAsMx3RF5FixOfSBm+pTnX7FrjXPHDk8Vul/r4QRiATtUeYviaLe+TfkXW==
a[ 9 ] = csm-hit=456.92
a[ 10 ] = ubid-main=400-3487403-7234182
a[ 11 ] = session-id-time=40034874037l
a[ 12 ] = session-id=778-6702530-6814121
Note, of course, that the array a doesn't actually exist as an addressable variable; I just refer to it that way here because I have to call it something. Now, at this point, I could actually go a couple of different way — I'll show you a second way below. The way I chose to go here was to convert this array into an object (which, in Javascript, is equivalent to what other languages call a hashmap or associative array) and then index that object.

To do that, first I apply a map to the initial array that splits it up into an array of arrays, split on the '=' value. I also trim the whitespace on both ends (if I didn't do this, I wouldn't be able to index the final object). It would be nice if there were some primitive way to tell Javascript to apply a trim() and split() to the object instances it were passed; you can't do this without defining another function. You can get close with bind, but trim is a regular expression that can't be bound, and you can't pass include arguments to a bound function.

javascript:alert( document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } ).reduce( 
function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ "session-token" ] );
a[ 0 ] = [ 'aws-x-main', '74TVFa0BSwhflf0oMuebP1IywvY4uek' ]
a[ 1 ] = [ '__utma', '3822618247.014730400.501177117.676853367.3513281.5' ]
a[ 2 ] = [ '__utmz', '37183305.21457436277.4.6.utmccn=(organic)|utmcsr=google|utmctr=|utmcmd=organic' ]
a[ 3 ] = [ 'aws-ubid-main', '371-8330521-4574362' ]
a[ 4 ] = [ 'aws-session-id-time', '5514168847l' ]
a[ 5 ] = [ 'aws-session-id', '677-3752086-8645371' ]
a[ 6 ] = [ 'apn-user-id', 'xbkws5k4-xray-sai4-onop-tjj4ttyw7kee' ]
a[ 7 ] = [ 'x-main', 'bUcjNIertj6ERMXXGO6b56jIyEBSRIbN' ]
a[ 8 ] = [ 'session-token', 'hy5a5/tPr/6r1ipwglUUF53fHF8l8PDu5J2RIva/SY0sL
tElkhyWSa4BQjS1cgGcsb+IJDvtTuQhOrLMc4fPpXKVrqf4Mxdk1+mcgM4hoGRKbOKhBLrdCQX
LPcrN474N1uC2EnDbf2/55NWdTYPEkQvNtIr5SxdnJ74OpamHKrSluwmUBVbPMV5nxTwS83gav
SEpHtMURWM8+DEVJAYDeH8Uv8taAsMx3RF5FixOfSBm+pTnX7FrjXPHDk8Vul/r4QRiATtUeYv
iaLe+TfkXW==' ]
a[ 9 ] = [ 'csm-hit', '456.92' ]
a[ 10 ] = [ 'ubid-main', '400-3487403-7234182' ]
a[ 11 ] = [ 'session-id-time', '40034874037l' ]
a[ 12 ] = [ 'session-id', '778-6702530-6814121' ]

Now for the coup-de-grace. I want to turn this array of arrays into a map of key-value pairs. I can do this with the reduce function:

javascript:alert( document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } ).reduce( 
function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ "session-token" ] );
reduce is a built-in implementation of the following loop:

for ( x in a )
{
  init = f( init, a[ x ] );
}

In other words, keep applying function f to init and each element of the array a, assigning init to the result each time. (If init is not supplied, the first value of the array is used as init). By using an empty object as init in this case, this expands conceptually to:

var init = {}
for ( x in a )
{
  init[ a[ 0 ] ] = a[ 1 ];
}

Since each element of a in this case is a two-element array, this distributes the contents of the array to the object as key-value pairs.

Finally, I index this new object to get back the value of the key that I'm interested in — in this case, the session-token value. There's one problem here, though - notice that what I get back is:

hy5a5/tPr/6r1ipwglUUF53fHF8l8PDu5J2RIva/SY0sLtElkhyWSa4BQ jS1cgGcsb+IJDvtTuQhOrLMc4fPpXKVrqf4Mxdk1+mcgM4hoGRKbOKhBL rdCQXLPcrN474N1uC2EnDbf 2/55NWdTYPEkQvNtIr5SxdnJ74OpamHKrSluwmUBVbPMV5nxTwS83gavS EpHtMURWM8+DEVJAYDeH8Uv 8taAsMx3RF5FixOfSBm+pTnX7FrjXPHDk8Vul/r4QRiATtUeYviaLe+TfkXW.

However, the actual session token value is Base64 encoded, and includes two '=' signs on the end. What happened? Well, when I split on '=', I ended up with two empty strings at the end, since split will split on all occurences of the search string. As it turns out, although a cookie value can't legally contain the ';' character, it can legally contain an '=' sign.

I can see a couple of ways to handle this. The first is to change the map function to a more explicit split:

map( function( x ) { return x.trim().split( '=' ); } )
map( function( x ) { return [ x.substring( 0, x.indexOf( '=' ) ).trim(), x.substring( x.indexOf( '=' ) + 1 ).trim() ] } )
But yikes; that adds 67 characters to my implementation. A shorter (but arguably harder to follow) approach is to return the delimiter in the split array and then join the end part back together in the reduce call:

alert( c.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( 
function( a, b ) { a[ b[ 0 ] ] = b.slice( 2 ).join( '' ); return a; }, {} )[ "session-token" ] );

This adds just 19 extra characters; by changing the split parameter to a regular expression, I get back the delimiters in my split array. Of course, that means that I have to join any equal signs back in at the end.

There's a related filter function that could be used in a similar way:

javascript:alert( document.cookie.split( ';' ).filter( function( s ) { return s.search( 'session-token=' ) == 1 } ) );
Here the return value is an array, and it includes the "session-token=" part at the beginning of the response. I can work around that by splitting up the returned value as before:
javascript:alert( document.cookie.split( ';' ).filter( function( s ) { 
return s.search( 'session-token=' ) == 1 } )[ 0 ].split( /(=)/ ).slice( 2 ).join( '' ) );
But that's not that much better (I only save 30 characters), and my search key is in the middle of the text, so I have to "hunt" for it if I want to change it. Also, it's important here that my search string include the trailing '=' character or it would match, say, session-token-aws; if I copy this into my URL bar and double click the word to change it, I might inadvertently overwrite the extra '='. Also, this approach fails silently when the cookie isn't present... so isn't much better than the regular expression approach I started out with.

Note that none of these approaches handles multiple values correctly. This can legitimately happen when you have cookies bound to different path or domain names and the current URL matches both (or more) of them. Although this probably represents some sort of an error condition, it's the sort of thing you, as the developer, would want to be aware of. A perfect implementation would deal with this cookie string:

a=1; a=2; a=3; b=4;
In this case, the first example returns '3' and the second returns '1'. I can work around this and still stay on a single line with the first approach (but not, as far as I can tell, with the second), at the cost of a painful bit of repetition:
document.cookie.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( function( a, b ) { 
a[ b[ 0 ] ] = a[ b[ 0 ] ] ? a[ b[ 0 ] ] + ', ' + b.slice( 2 ).join( '' ) :  
b.slice( 2 ).join( '' ); return a; }, {} )[ "session-token" ];
But... whoa.

I can take this even a step farther and prompt for the value of the cookie to return like this:

document.cookie.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( 
function( a, b ) { a[ b[ 0 ] ] = a[ b[ 0 ] ] ? a[ b[ 0 ] ] + ', ' + b.slice( 2 ).join( '' ) : 
b.slice( 2 ).join( '' ); return a; }, {} )[ prompt( 'Which Cookie?' ) ];
In fact, this is bookmarklet territory — for example: cookielookup. Of course, you have to know the name of the cookie you're looking for... an even better implementation might present a drop-down of available cookies. On the other hand, if you've already gotten to that point, it's probably time to bite the bullet and install HttpFox anyway.

Add a comment:

Completely off-topic or spam comments will be removed at the discretion of the moderator.

You may preserve formatting (e.g. a code sample) by indenting with four spaces preceding the formatted line(s)

Name: Name is required
Email (will not be displayed publicly):
Comment:
Comment is required
Beki, 2013-07-02
Oh my goodness! a fatiastnc info man. Thnx However I’m having problem with your RSS feed. Unable to subscribe. So anybody else getting identical RSS feed trouble? Anybody who can help please respond. TQ
Josh, 2013-07-03
What problems are you having? What browser are you using? The RSS feed is working for me in Mozilla.
Aileen, 2013-07-02
I have been searching for a llttie for any high-quality articles and blog posts in this type of field . After sufing in Google, I finally came across your web site. After studying this material, I knew I have discovered exactly what I need. Thnx!
Reader, 2013-10-24
Horrible page.... pls hire some designers ;)
Josh, 2013-10-25
Sure. As soon as my ad revenue tops $100K. ; )
D, 2016-03-01
and finally ? how do you get a cookie value after all that brain wanking ?...
Anonymous, 2016-09-06
Hi see this site: habbo.com.br It have a cookie name 'session.id' how may I get this cookie with javascript?
My Book

I'm the author of the book "Implementing SSL/TLS Using Cryptography and PKI". Like the title says, this is a from-the-ground-up examination of the SSL protocol that provides security, integrity and privacy to most application-level internet protocols, most notably HTTP. I include the source code to a complete working SSL implementation, including the most popular cryptographic algorithms (DES, 3DES, RC4, AES, RSA, DSA, Diffie-Hellman, HMAC, MD5, SHA-1, SHA-256, and ECC), and show how they all fit together to provide transport-layer security.

My Picture

Joshua Davies

Past Posts