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!
The
express-auth
module supports multiple authentication schemes, including HTTP Digest. See: https://github.com/ciaranj/express-authAnother excellent option is
passport
at: https://github.com/jaredhanson/passportThe 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 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 aroundnc
, and maybe alsoqop
qop
value gathercontent is using. If it'sauth-int
, then you'd also have to append the hashed HTTP body toHA2
, 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 forqop
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 acnonce
,nc
etc.parsedDigest['Digest realm']
, i.e. you are assuming that therealm
is the first attribute after the initialDigest
keyword. That might or might not be the case, but you should not rely upon it (modify yourparseDigest
function to strip of the string"Digest "
before splitting the rest)parsedDigest
, you make the assumption that Digest is always capitalized that way, and thatrealm
,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 doBasic 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:
It's Peter from GatherContent.
The first, pretty obvious thing would be to use just
get_me
instead ofget_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
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.