I'm trying to get a simple (!) digest authentication working with node js using an an API from gathercontent.com.
Everything seems to be working except I still get a "Wrong credentials" response that looks like this:
{ success: false, error: 'Wrong Credentials!' }
The code looks like this:
var https = require('https'),
qs = require('querystring');
apikey = "[my api key goes in here]",
pwd = "[my password goes in here]",
crypto = require('crypto');
module.exports.apiCall = function () {
var options = {
host:'abcdefg.gathercontent.com',
port:443,
path:'/api/0.1/get_pages_by_project/get_me',
method:'POST',
headers:{
"Accept":"application/json",
"Content-Type":"application/x-www-form-urlencoded"
}
};
var req = https.request(options, function (res) {
res.on('data', function (d) {
var creds = JSON.parse(d);
var parsedDigest = parseDigest(res.headers['www-authenticate']);
console.log(parsedDigest);
var authopts = {
host:'furthercreative.gathercontent.com',
port:443,
path:'/api/0.1/get_pages_by_project/get_me',
method:'POST',
headers:{
"Accept":"application/json",
"Content-Type":"application/x-www-form-urlencoded",
"Authorization" : getAuthHeader(parsedDigest, apikey, parsedDigest['Digest realm'], pwd)
}
};
console.log(authopts);
console.log('\n\n\n');
var req2 = https.request(authopts, function (res2) {
console.log("statusCode: ", res2.statusCode);
console.log("headers: ", res2.headers);
res2.on('data', function (d2) {
var result = JSON.parse(d2);
});
});
req2.end();
});
});
req.write('id=1234');
req.end();
req.on('error', function (e) {
console.error(e);
});
};
function parseDigest(s){
var parts = s.split(',');
var obj = {};
var nvp = '';
for(var i = 0; i < parts.length; i++){
nvp = parts[i].split('=');
obj[nvp[0]] = nvp[1].replace(/"/gi, '');
}
return obj;
}
function getAuthHeader(digest, apikey, realm, pwd){
var md5 = crypto.createHash('md5');
var s = '';
var nc = '00000001';
var cn = '0a4f113b';
var HA1in = apikey+':'+realm+':'+pwd;
md5 = crypto.createHash('md5');
md5.update(HA1in);
var HA1out = md5.digest('hex');
var HA2in = 'POST:/api/0.1/get_pages_by_project/get_me';
md5 = crypto.createHash('md5');
md5.update(HA2in);
var HA2out = md5.digest('hex');
md5 = crypto.createHash('md5');
var respIn = HA1out + ':' + digest.nonce + ':'+nc+':'+cn+':'+digest.qop+':'+ HA2out;
md5.update(respIn);
var resp = md5.digest('hex');
s = [ 'Digest username="',apikey,'", ',
'realm="',digest['Digest realm'],'", ',
'nonce="',digest.nonce,'", ',
'uri="/api/0.1/get_pages_by_project/get_me", ',
'cnonce="',cn,'", ',
'nc="',nc,'", ',
'qop="',digest.qop,'", ',
'response="',resp,'", ',
'opaque="',digest.opaque,'"'].join('')
return s;
}
I'd try and Curl to it but I'm not sure how!
Any help appreciated!
I see a couple of issues potentially related to your problem. It's hard to tell which ones are the actual culprits, not knowing anything about gathercontent's implementation. If you pasted an example of their 'WWW-Authenticate' header, it would be much easier to provide specific help.
So I'm speculating what the actual cause is, but here are some actual problems that you should address anyway, to conform to the spec (i.e. protect it from breaking in the future because the server starts doing things slightly differently):
Authorization
headers you are creating, remove the double quotes around nc
, and maybe also qop
qop
value gathercontent is using. If it's auth-int
, then you'd also have to append the hashed HTTP body to HA2
, see #3.2.2.3 of the spec - furthermore, they might be specifying a comma-separated list of qop values for you to choose from - or the server might not send a value for qop
at all, i.e. they use the most basic from of HTTP digest auth, in which your implementation would be violating the spec, as then you aren't allowed to e.g. send a cnonce
, nc
etc.parsedDigest['Digest realm']
, i.e. you are assuming that the realm
is the first attribute after the initial Digest
keyword. That might or might not be the case, but you should not rely upon it (modify your parseDigest
function to strip of the string "Digest "
before splitting the rest)parsedDigest
, you make the assumption that Digest is always capitalized that way, and that realm
, nonce
, etc. are always in lowercase. According to the spec, these are all case-insensitiveA couple of unrelated issues:
Digest authentication
? This is HTTPS, so you might as well do Basic authentication
, it's way easier, and with HTTPS, just as safe. (Answering myself here, after checking out gathercontent: Basic auth is apparently not possible)cnonce
should be random for every request, especially, you shouldn't copy and paste it from Wikipedia, which makes you more vulnerable (but not an issue here, as all data goes over SSL anyway in your case)Regarding how to curl it - try this:
curl --data 'id=1234' --digest --user "apikey:pwd" https://abcdefg.gathercontent.com:443/api/0.1/get_pages_by_project/get_me
It's Peter from GatherContent.
The first, pretty obvious thing would be to use just get_me
instead of get_pages_by_project/get_me
. You are mixing two different things in the latter. get_me
doesn't require any parameters sent via POST, so you can drop them.
Also, please make sure that your password is always lowercase x
.
Does it change anything?
Edit: For anyone interested, here's our API docs: http://gathercontent.helpjuice.com/questions/26611-How-do-I-use-the-API
The express-auth
module supports multiple authentication schemes, including HTTP Digest. See: https://github.com/ciaranj/express-auth
Another excellent option is passport
at: https://github.com/jaredhanson/passport
The http-digest examples in the two modules tend to focus on establishing an authentication for you node.js application vs. forwarding the authentication request to a third-party. However, you should be able to make it work with a little noodling.
If pressed, i would use passport. The examples offered are a lot more clear and documented.
Hope that helps...
I would recomand you to use mikeal's request module it make it a lot easier and cleaner.
Request does not have support for HTTP Auth yet, saddly but you would just have to set the Authorization
header.