Search code examples
javascriptnode.jsflickr

Flickr API throws up "Invalid API Key (Key has invalid format)" when POSTing data with oauth signing


I can't seem to find this answered anywhere on SO or even Google - I have an oauth-signed call to the Flickr upload API, and following the docs I've signed the POST operation the usual oauth way (but without the photo data). For testing purposes I've only passed along a title and the photo data, which means I end up a var flickrURI that contains the following url for POSTing to:

https://api.flickr.com/services/upload/
? format=json
& oauth_consumer_key=...
& oauth_nonce=2e57b73fec6630a30588e22383cc3b25
& oauth_signature_method=HMAC-SHA1
& oauth_timestamp=1411933792346
& oauth_token=...
& title=test
& oauth_signature=O7JPn1m06vl5Rl95Z2in32YWp7Q%3D

(split over multiple lines for legibility in this question, the actual URL has no whitespacing around the ? and & for obvious reasons).

The oauth signing itself is quite correct, and code used for accessing the not-upload-API all over the place with correct behaviour, so it seems pretty much impossible for that to get the signing wrong, other than perhaps signing with "not enough data" or perhaps signing with "too much data".

The auth signing first forms the auth query string, in this case:

oauth_consumer_key=...
&oauth_nonce=60028905f65cf9d7649b3bce98f718f8
&oauth_signature_method=HMAC-SHA1
&oauth_timestamp=1411939726691
&oauth_token=...
&title=test

which is then used to form the verb + address + encoded base string:

POST&https%3A%2F%2Fapi.flickr.com%2Fservices%2Fupload%2F&oauth_consumer_key%3D...%26oauth_nonce%3D60028905f65cf9d7649b3bce98f718f8%26oauth_signature_method%3DHMAC -SHA1%26oauth_timestamp%3D1411939726691%26oauth_token%3D...%26title%3Dtest

This is then HMAC-SHA1 digested using the Flickr and oauth secrets:

function sign = (data, key, secret) {
  var hmacKey = key + "&" + (secret ? secret : ''),
      hmac = crypto.createHmac("SHA1", hmacKey);
  hmac.update(data);
  var digest = hmac.digest("base64");
  return encodeURIComponent(digest);
}

And for GET requests, this works perfectly fine. For POST requests things seem to be difference, despite the docs not explain which part is supposedly different, so I the tried to use the Nodejs request package to perform the POST action in what seemed a normal way, using:

uploadOptions = {
  oauth_consumer_key = auth.api_key,
  oauth_nonce = auth.oauth_nonce,
  oauth_timestamp = auth.oauth_timestamp,
  oauth_token = auth.access_token,
  oauth_signature_method = "HMAC-SHA1",
  title: title,
  photo: <binary data buffer>
};

flickrURL = formSignedURL(auth);

request.post({
  url: flickrURI,
  headers: {
    "Authorization": 'oauth_consumer_key="...",oauth_token="...",oauth_signature_method="HMAC-SHA1",oauth_signature="...",oauth_timestamp="...",oauth_nonce="...",oauth_version="1.0"'
  },
  multipart: [{
    'content-type': 'application/json',
    body: JSON.stringify(signOptions)
  }]
},function(error, response, body) {
    console.log("error");
    console.log(error);
    console.log("body");
    console.log(body);
  }
);

which yields a body that contains:

<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="fail">
  <err code="100" msg="Invalid API Key (Key has invalid format)" />
</rsp>

As the oauth signing doesn't really give me a choice in which API key to provide (there is only one) and the signing works just fine for the not-upload API, I can't figure out what this error message is trying to tell me. The key is definitely the right format because that's the format Flickr gives you, and it's the correct value, because it works just fine outside of uploading. I also made sure to get the oauth token and secret for that key with "delete" permission (widest possible permissions) so the included access token and access token secret should pass the "does this token for this key have permissions to write" test.

What obvious thing am I missing here that's preventing the upload to go through?


Solution

  • It turns out adding the data as request.post multipart information isn't good enough, and will make the Flickr API throw an "Invalid API Key (Key has invalid format)" error instead of saying what's actually wrong. The following request call, with exactly the same data, works:

    var uploadOptions = ...
    
    var flickrURL = ...;
    
    var req = request.post(flickrURL, function(error, response, body) {
      callback(error, body);
    });
    
    var form = req.form();
    uploadOptions.photo = fs.createReadStream(...);
    Object.keys(photoOptions).forEach(function(prop) {
      form.append(prop, photoOptions[prop]);
    });
    

    Despite not making all that much sense call wise (why would the POST not already be done by the time we call form = req.form()?) this is request's "proper" way to send the POST payload over the wire, and makes the Flickr API process the photo upload just fine.