Get and Set a Single Cookie with Node.js HTTP Server - javascript

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.

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.

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 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

As an enhancement to #Corey Hart's answer, I've rewritten the parseCookies() using:
RegExp.prototype.exec - use regex to parse "name=value" strings
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);

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:
Cookie Syntax
Set-Cookie Syntax
Additional information:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie

You can use the "cookies" npm module, which has a comprehensive set of features.
Documentation and examples at:
https://github.com/jed/cookies

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']

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

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

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}`)
}
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.

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
});
});

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);
}

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
}

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.

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`
});

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

Related

url.parse() returns undefined object keys

when i use url.parse(from the url module in node.js)it returns undefined object keys
i kept searching on internet and apparently most people use express.js so i didnt find any case similar to mine
here is the code(shortened):
var http = require('http');
var Url = require('url');
http.createServer(function(req, res) {
if (req.url != '/favicon.ico') {
var q = Url.parse(req.url, true);
console.log(req.url);
for (var key in q) {
console.log(key + ':' + q.key);
}
console.log('closing');
res.end();
}
}).listen(8080);
and the console displays this :
/?season=summer
protocol:undefined
slashes:undefined
auth:undefined
host:undefined
port:undefined
hostname:undefined
hash:undefined
search:undefined
query:undefined
pathname:undefined
path:undefined
href:undefined
parse:undefined
format:undefined
resolve:undefined
resolveObject:undefined
parseHost:undefined
closing
You need to use the query property of the parsed Url like you initially had it. You just need to access the value of the query object correctly.
Check the simplified example below (Note: queryData[key]):
const url = require('url');
const queryData = url.parse('http://127.0.0.1:8000/status?season=summer', true).query;
for (const key in queryData) {
console.log(`${key}: ${queryData[key]}`);
}
Edit:
The explanation for why you can't do something like queryData.key is that that would try to access a property in queryData object named 'key' and not the current key variable value (in this case 'season').
In this instance, the queryData object looks like this:
var queryData = {
'season': 'summer'
};
If you try to access queryData.key it is looking for the literal key called 'key' and not the value of the variable called key which happens to be season in the first iteration of the for in loop. Using queryData[key] allows you to access the value of the key assigned to the key variable.
Instead of: var q = Url.parse(req.url, true).query; you need to use var q = Url.parse(req.url, true);.
Otherwise q will already be Url.parse().query and the reset will not work/be undefined.

Node.js Cookies not working

So I'm hosting a node.js file on my website, and I'm trying to get data through cookies.
I am using editmycookie, and I can see that the cookies ARE set.
I have a function that gets the cookies from a name
function parseCookies(request, finding){
rc = request.headers.cookie + ';';
rc && rc.split(';').forEach(function( cookie ) {
var parts = cookie.split('=');
if(parts.shift().trim() == finding){
return decodeURI(parts.join('=')).replace(/-/g, '=');
}
});
}
Then I run this, knowing the cookies are set
app.get('/', function(req, res){
user = parseCookies(req, 'user');
pass = parseCookies(req, 'pass');
console.log(user + pass);
and it logs as NaN
I'm still learning Node, sorry.
I'm on Ubuntu 16.04 if that helps!\
Weirdly enough, it works for some users on my site, and for some it doesn't. I haven't noticed a pattern of whose work and whose do not.
You do not return anything from the parseCookies function, the return decodeURI(parts.join('=')).replace(/-/g, '=') is called with the callback of the foreach, so this return is meaningless.
Because for that parseCookies returns undefined and undefined + undefined is NaN.
When using forEach you need to save the matched result in an temporary variable result, and return this one.
function parseCookies(request, finding) {
var result;
var rc = request.headers.cookie + ';';
rc && rc.split(';').forEach(function(cookie) {
var parts = cookie.split('=');
if (parts.shift().trim() == finding) {
result = decodeURI(parts.join('=')).replace(/-/g, '=');
}
});
return result;
}
Or use Array.prototype.find (I didn't have time to test that version so there might be a bug in it):
function parseCookies(request, finding) {
var rc = request.headers.cookie + ';';
var cookie = rc.split(';').find(cookie => cookie.split('=').shift().trim() == finding);
return decodeURI(cookie.split('=').join('=')).replace(/-/g, '=');
}
But why do you parse the cookies your self anyway. There are robust and well tested middlewares that will do this for you.
Set the cookies using this code and lets see how it works
var http = require('http');
function parseCookies (request) {
var list = {},
rc = request.headers.cookie;
rc && rc.split(';').forEach(function( cookie ) {
var parts = cookie.split('=');
list[parts.shift().trim()] = decodeURI(parts.join('='));
});
return list;
}
http.createServer(function (request, response) {
// To Read a Cookie
var 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);
console.log('Server running at http://127.0.0.1:8124/');
And add your routes to which you want to call.Hope this hepls for you.

Parsing cookies with socket.io

I am trying to properly read cookies on my node server that were set by me through the browser console on localhost:3000 like this:
document.cookie = "tagname = test;secure";
document.cookie = "hello=1"
In my node server, I use sockets.io, and when I get a connection request, I can access a property which goes like this:
socket.request.headers.cookie
It's a string, and I always see it like this:
'io=QhsIVwS0zIGd-OliAAAA' //what comes after io= is random.
I've tried to translate it with various modules but they can't seem to parse the string. this is my latest attempt:
var cookie = require('cookie');
io.sockets.on('connection', function(socket) {
socket.on('addUser', function(){
var a = socket.request.headers.cookie;
var b = cookie.parse(a); //does not translate
console.log(b);
});
}
I obviously want to get an object with all the cookies that were sent by each io.connect on the browser.
I've been trying to solve it for 5 hours and I really don't know what I am doing wrong here.
Use the Cookie module. It is exactly what you are looking for.
var cookie = require('cookie');
cookie.parse(str, options)
Parse an HTTP Cookie header string and returning an object of all cookie name-value pairs. The str argument is the string representing a Cookie header value and options is an optional object containing additional parsing options.
var cookies = cookie.parse('foo=bar; equation=E%3Dmc%5E2');
// { foo: 'bar', equation: 'E=mc^2' }
Hope this helps
Without Regexp
//Get property directly without parsing
function getCookie(cookie, name){
cookie = ";"+cookie;
cookie = cookie.split("; ").join(";");
cookie = cookie.split(" =").join("=");
cookie = cookie.split(";"+name+"=");
if(cookie.length<2){
return null;
}
else{
return decodeURIComponent(cookie[1].split(";")[0]);
}
}
//getCookie('foo=bar; equation=E%3Dmc%5E2', 'equation');
//Return : "E=mc^2"
Or if you want to parse the cookie to object
//Convert cookie string to object
function parseCookie(cookie){
cookie = cookie.split("; ").join(";");
cookie = cookie.split(" =").join("=");
cookie = cookie.split(";");
var object = {};
for(var i=0; i<cookie.length; i++){
cookie[i] = cookie[i].split('=');
object[cookie[i][0]] = decodeURIComponent(cookie[i][1]);
}
return object;
}
//parseCookie('tagname = test;secure');
//Return : {tagname: " test", secure: "undefined"}
Try using socket.handshake instead of socket.request
The IO cookie is the default cookie socket.io uses as a user id. You can set this but if you don't it will create one and set a hash value to it.
Read about the option here.
I don't think it is a code issue. Here is an example of your code. When I added the cookie test and set it to 1
var app = require('express')();
var http = require('http').Server(app);
var cookie = require('cookie')
var io = require('socket.io')(http);
var port = process.env.PORT || 3000;
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
io.sockets.on('connection', function(socket) {
socket.on('chat message', function(){
var a = socket.request.headers.cookie;
var b = cookie.parse(a); //does not translate
console.log(b);
});
});
http.listen(port, function(){
console.log('listening on *:' + port);
});
Server Output
{ io: 'TxvLfvIupubZpOaGAAAF', test: '1' }
If I changed the it to this.
var io = require('socket.io')(http, {
cookie : 'id'
});
The output would change this to.
{ id: 'ZJPSwFsQAje0SrgsAAAD', test: '1' }

Not understanding Cookies in Node.js

I am making a program in Node.js that involves cookies. I don't want to use a library like express. I found the following code online for using cookies, but I am not exactly sure how it works. Could somebody break it down for me? Also, I am not sure which part of the code reads cookies in the system and which part writes them. Could you clarify that as well?
Thank you:
Here is the code:
var http = require('http');
function parseCookies(cookie) {
return cookie.split(';').reduce(
function(prev, curr) {
var m = / *([^=]+)=(.*)/.exec(curr);
var key = m[1];
var value = decodeURIComponent(m[2]);
prev[key] = value;
return prev;
},
{ }
);
}
function stringifyCookies(cookies) {
var list = [ ];
for (var key in cookies) {
list.push(key + '=' + encodeURIComponent(cookies[key]));
}
return list.join('; ');
}
http.createServer(function (request, response) {
var 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);
Cookies are just a piece of text that's sent as a header when the browser sends a request to a server. The server can then modify the cookies if it wants, and send them back as a header to the browser.
The convention for cookies is that they are key-value pairs separated by an ampersand (&), just like a query string in a URL (which is why decodeURIComponent and encodeURIComponent work in your example!).
parseCookies reads from the cookie string to an object representing your cookies. Eg.
// Input
"foo=bar&baz=42"
// Output
{foo: "bar", baz: 42}
stringifyCookies takes that cookie object, and converts it back to a cookie:
// Input
{foo: "bar", baz: 42}
// Output
"foo=bar&baz=42"
Does that make sense?

Using JavaScript to properly sign a string using HmacSHA256

In the Houndify API Docs for Authentication, you have the following block of content:
An Example of Authenticating a Request
Let's assume we have the following information:
UserID: ae06fcd3-6447-4356-afaa-813aa4f2ba41
RequestID: 70aa7c25-c74f-48be-8ca8-cbf73627c05f
Timestamp: 1418068667
ClientID: KFvH6Rpy3tUimL-pCUFpPg==
ClientKey: KgMLuq-k1oCUv5bzTlKAJf_mGo0T07jTogbi6apcqLa114CCPH3rlK4c0RktY30xLEQ49MZ-C2bMyFOVQO4PyA==
Concatenate the UserID string, RequestID string, and TimeStamp string in the following format: {user_id};{request_id}{timestamp}
With the values from the example, the expected output would be in this case: ae06fcd3-6447-4356-afaa-813aa4f2ba41;70aa7c25-c74f-48be-8ca8-cbf73627c05f1418068667
Sign the message with the decoded ClientKey. The result is a 32-byte binary string (which we can’t represent visually). After base-64 encoding, however, the signature is: myWdEfHJ7AV8OP23v8pCH1PILL_gxH4uDOAXMi06akk=
The client then generates two authentication headers Hound-Request-Authentication and Hound-Client-Authentication.
The Hound-Request-Authentication header is composed by concatenating the UserID and RequestID in the following format: {user-id};{request-id}. Continuing the example above, the value for this header would be:
Hound-Request-Authentication: ae06fcd3-6447-4356-afaa-813aa4f2ba41;70aa7c25-c74f-48be-8ca8-cbf73627c05f
The Hound-Client-Authentication header is composed by concatening the ClientID, the TimeStamp string and the signature in the following format: {client-id};{timestamp};{signature}. Continuing the example above, the value for this header would be: Hound-Client-Authentication: KFvH6Rpy3tUimL-pCUFpPg==;1418068667;myWdEfHJ7AV8OP23v8pCH1PILL_gxH4uDOAXMi06akk=
For Number 3, it says "Sign the message with the decoded ClientKey". The "message" and "ClientKey" are two distinct strings.
My question(s): How do you sign one string with another string i.e. what exactly does that mean? And how would you do that in JavaScript?
var message = 'my_message';
var key = 'signing_key';
//??what next??
I'm trying to figure all this out so I can create a pre-request script in Postman to do a proper HmacSHA256 hash.
According to the documentation, if you're using one of their SDKs, it will automatically authenticate your requests:
SDKs already handle authentication for you. You just have to provide
the SDK with the Client ID and Client Key that was generated for your
client when it was created. If you are not using an SDK, use the code
example to the right to generate your own HTTP headers to authenticate
your request.
However, if you want to do it manually, I believe you need to compute the HMAC value of the string they describe in the link in your question and then send it base64 encoded as part of the Hound-Client-Authentication header in your requests. They provide an example for node.js:
var uuid = require('node-uuid');
var crypto = require('crypto');
function generateAuthHeaders (clientId, clientKey, userId, requestId) {
if (!clientId || !clientKey) {
throw new Error('Must provide a Client ID and a Client Key');
}
// Generate a unique UserId and RequestId.
userId = userId || uuid.v1();
// keep track of this requestId, you will need it for the RequestInfo Object
requestId = requestId || uuid.v1();
var requestData = userId + ';' + requestId;
// keep track of this timestamp, you will need it for the RequestInfo Object
var timestamp = Math.floor(Date.now() / 1000),
unescapeBase64Url = function (key) {
return key.replace(/-/g, '+').replace(/_/g, '/');
},
escapeBase64Url = function (key) {
return key.replace(/\+/g, '-').replace(/\//g, '_');
},
signKey = function (clientKey, message) {
var key = new Buffer(unescapeBase64Url(clientKey), 'base64');
var hash = crypto.createHmac('sha256', key).update(message).digest('base64');
return escapeBase64Url(hash);
},
encodedData = signKey(clientKey, requestData + timestamp),
headers = {
'Hound-Request-Authentication': requestData,
'Hound-Client-Authentication': clientId + ';' + timestamp + ';' + encodedData
};
return headers;
};
So basically, signing [in this specific case] simply means to create a hash of the string using a hash algorithm in addition to a key, as opposed to a keyless hash [like MD5]. For example:
var message = 'my_message';
var key = 'signing_key';
var hashed_message = hash_func(message, key);
where hash_func is a hashing algorithm like HmacSHA256 (the hashing algorithm in question).
The reason I was trying to figure this out was to test authentication for the Houndify API using Postman. Fortunately, Postman has a nice feature called pre-request scripts [complete with hashing algorithms] that helps if you need to pre-generate values that need to be sent along with your request.
After much fiddling around, I managed to create a pre-request script that properly authenticates to this API (see code below).
var unescapeBase64Url = function (key) {
return key.replace(/-/g, '+').replace(/_/g, '/');
},
escapeBase64Url = function (key) {
return key.replace(/\+/g, '-').replace(/\//g, '_');
},
guid = function() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
};
var client_id_str = environment["client-id"];
var client_key_str = environment["client-key"];
var user_id_str = environment["user-id"];
var request_id_str = guid();
var timestamp_str = Math.floor(Date.now() / 1000);
var client_key_dec_str = CryptoJS.enc.Base64.parse(unescapeBase64Url(client_key_str));
var message_str = user_id_str+";"+request_id_str+timestamp_str;
var signature_hash_obj = CryptoJS.HmacSHA256(message_str, client_key_dec_str);
var signature_str = signature_hash_obj.toString(CryptoJS.enc.Base64);
var hound_request_str = user_id_str+";"+request_id_str;
var hound_client_str = client_id_str+";"+timestamp_str+";"+escapeBase64Url(signature_str);
postman.setEnvironmentVariable("hound-request-authentication", hound_request_str);
postman.setEnvironmentVariable("hound-client-authentication", hound_client_str);
Note that you are going to have to create environment variables in Postman for client-id, client-key, and user-id, as well as for the header variables hound-request-authentication and hound-client-authentication to hold the final values that will be referenced when defining headers.
Hope it helps.

Categories

Resources