PedroSG
PedroSG

Reputation: 506

OAuth1.0 header in Node.js

I've been using an API via postman that uses OAuth1.0, successfully. Now I'm building an API that calls this API but I'm having trouble when trying to set up the equivalent in javascript of the OAuth1.0. The header looks like this:

'Authorization': 'OAuth oauth_consumer_key="XXX",oauth_token="XXX",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1559312415",oauth_nonce="XXX",oauth_version="1.0",oauth_signature="XXX"'

My problem is related to oauth_nonce and oauth_signature. What are the hash function that I can use to generate those 2 parameters.
Also, I'm using AXIOS for the request.
Thanks for your time.

Upvotes: 13

Views: 15965

Answers (4)

Rajesh Kumar Kanumetta
Rajesh Kumar Kanumetta

Reputation: 322

Simply we can pass oauth without library. as follows below.



var options = {
  'method': 'GET',
  'url': 'xxxx',
  'oauth': {
    consumer_key: CONSUMERKEY,
    consumer_secret: CONSUMERSECRET,
    token: TOKENKEY,
    token_secret: TOKENSECRET
  },
  'headers': {},
}

request(options, function (error, response) {
  if (error) console.log(error);
  console.log('response', response.body)
});

Upvotes: -1

Abraham Rosa Vargas
Abraham Rosa Vargas

Reputation: 1

This is an update to the solution that Greg Van Gorp raised earlier, but uses Crypto-JS. Since crypto is deprecated.

const crypto = require('crypto-js');
const oauth1a = require('oauth-1.0a');

const CONSUMERKEY = '<consumerKey>';
const CONSUMERSECRET = '<consumerSecret>';

class Oauth1Helper {
  static getAuthHeaderForRequest(request) {
    const oauth = oauth1a({
      consumer: {key: CONSUMERKEY, secret: CONSUMERSECRET},
      signature_method: 'HMAC-SHA1',
      hash_function(base_string, key) {
        return crypto.algo.HMAC
          .create(crypto.algo.SHA1, key)
          .update(base_string)
          .finalize()
          .toString(crypto.enc.Base64);
      },
    });

    const authorization = oauth.authorize(request);

    return oauth.toHeader(authorization);
  }
}

module.exports = Oauth1Helper;

I propose the solution a little differently since the API that it consumed did not require Tokens, but I understand that just passing the Tokens should work without problems, these are optional in the oauth1a.authorize method.

The request:

const request = {
         url: 'https://api-domain.com',
         method: 'POST',
         body: {
           someData: '1234',
         },
      };

const authHeader = Oauth1Helper.getAuthHeaderForRequest(request);

axios
  .post(request.url, request.body, {headers: authHeader})
  .then(res => {
     console.log(res);
  })
  .catch(err => {
     console.log(err);
  });

I hope it works!

Upvotes: -1

webjay
webjay

Reputation: 5488

Here's one that doesn't need a package.

You'll need makeHeader(consumer, token, request) which works for me with Node's https.request but should also work for Axios.

const crypto = require('crypto');
const { stringify: qStringify } = require('querystring');
const { httpOptions, fetch } = require('./fetch');

function nonce() {
  return crypto.randomBytes(16).toString('hex');
}

function sign(baseStr, key) {
  return crypto.createHmac('sha1', key).update(baseStr).digest('base64');
}

function percentEncode(str) {
  const notEscapedRe = /[!'()*]/g;
  return encodeURIComponent(str).replace(notEscapedRe, (c) => `%${c.charCodeAt(0).toString(16)}`);
}

function makeObjStr(parameters, quote = '"', split = ',') {
  const ordered = Object.fromEntries(Object.entries(parameters).sort());
  return Object.entries(ordered).map(([key, value]) => `${percentEncode(key)}=${quote}${percentEncode(value)}${quote}`).join(split);
}

function authHeader(parameters) {
  return { Authorization: `OAuth ${makeObjStr(parameters)}` };
}

function makeHeader(consumer, token, request) {
  const oauthData = {
    oauth_consumer_key: consumer.key,
    oauth_token: token.key,
    oauth_nonce: nonce(),
    oauth_signature_method: 'HMAC-SHA1',
    oauth_timestamp: Math.floor(Date.now() / 1000),
    oauth_version: '1.0',
  };
  const baseStr = [
    request.method.toUpperCase(),
    percentEncode(request.url),
    percentEncode(makeObjStr({ ...request.data, ...oauthData }, '', '&')),
  ].join('&');
  const signingKey = [percentEncode(consumer.secret), percentEncode(token.secret)].join('&');
  return authHeader({
    ...oauthData,
    oauth_signature: sign(baseStr, signingKey),
  });
}

function oAuth1Fetch({
  consumer, token, hostname, path, query = {},
}) {
  const request = {
    method: 'GET',
    url: `https://${hostname}${path}`,
    data: query,
  };
  return fetch({
    ...httpOptions,
    headers: makeHeader(consumer, token, request),
    hostname,
    path: `${path}?${qStringify(query)}`,
  });
}

module.exports = {
  oAuth1Fetch,
};

Here's my fetch:

const { Agent, request } = require('https');

const httpOptions = {
  agent: new Agent({ keepAlive: true }),
  'User-Agent': `AWS Lambda Node/${process.version} surflog.app`,
  // Accept: 'application/json',
};

function fetch(options) {
  return new Promise((resolve, reject) => {
    const req = request(options, (res) => {
      const data = [];
      res.on('data', (chunk) => data.push(chunk));
      res.on('end', () => {
        const result = Buffer.concat(data).toString();
        console.log(res.statusCode);
        if (res.statusCode >= 200 && res.statusCode < 300) {
          resolve(result);
        } else {
          reject(result);
        }
      });
    });
    req.setTimeout(6000, (err) => {
      console.warn(err);
      console.warn('Timeout', options.hostname);
    });
    req.on('error', reject);
    req.end();
  });
}

module.exports = {
  httpOptions,
  fetch,
};

An example:

oAuth1Fetch({
  consumer: {
    key: 'xyz',
    secret: 'xyz',
  },
  token: {
    key: 'xyz',
    secret: 'xyz',
  },
  hostname: 'apis.garmin.com',
  path: '/wellness-api/rest/backfill/activities',
  query: {
    summaryStartTimeInSeconds: 1609459200,
    summaryEndTimeInSeconds: 1609459200 + 7776000,
  },
}).then(console.log).catch(console.error);

Upvotes: 3

Greg Van Gorp
Greg Van Gorp

Reputation: 801

I was able to figure out a solution with Axios. I created an OauthHelper class to generate the Authorization header:

const crypto = require('crypto');
const oauth1a = require('oauth-1.0a');

const CONSUMERKEY = '<consumerKey>';
const CONSUMERSECRET = '<consumerSecret>';
const TOKENKEY = '<tokenKey>';
const TOKENSECRET = '<tokenSecret>';

class Oauth1Helper {
    static getAuthHeaderForRequest(request) {
        const oauth = oauth1a({
            consumer: { key: CONSUMERKEY, secret: CONSUMERSECRET },
            signature_method: 'HMAC-SHA1',
            hash_function(base_string, key) {
                return crypto
                    .createHmac('sha1', key)
                    .update(base_string)
                    .digest('base64')
            },
        })

        const authorization = oauth.authorize(request, {
            key: TOKENKEY,
            secret: TOKENSECRET,
        });

        return oauth.toHeader(authorization);
    }
}

module.exports = Oauth1Helper;

Then I was just able to make the post from wherever I need via Axios:

const request = {
    url: 'https://api-domain.com',
    method: 'POST',
    body: {
        "uniqueId": 1234
    }
};

const authHeader = Oauth1Helper.getAuthHeaderForRequest(request);

return await axios.post(
    request.url,
    request.body,
    { headers: authHeader });

Upvotes: 31

Related Questions