Authorizing Twitter API Calls in Javascript

I have been thinking of playing around Twitter APIs since a long time, but never sat down patiently to get it done. Procrastination is at its highest 😥 Anyways, I did it finally, with Twick - an Angular app to fetch and display Twitter data.

Getting Authorization header

Twitter has a good documentation on how to authorize a request.

Lets take an example request:

GET https://api.twitter.com/1.1/users/show.json?screen_name=void_imagineer
Authorization: OAuth
    oauth_consumer_key="XXxxXXxxXXXXXxxx",
    oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
    oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
    oauth_signature_method="HMAC-SHA1",
    oauth_timestamp="1318622958",
    oauth_token="ddddddd-xxxXXxxxxXXxxXXXXXxxxxxXXXXxxxxXXxxxxXX",
    oauth_version="1.0"
OAuth Fields value
oauth_consumer_key Get from apps.twitter.com
oauth_nonce Any unique random string (eg: base64 encoding of consumerKey & timestamp)
oauth_signature Described in next section
oauth_signature_method HMAC-SHA1
oauth_timestamp unix timestamp
oauth_token Get from apps.twitter.com
oauth_version 1.0


Twitter access keys are stored in keys.json only for local testing.

TWITTER_CONSUMER_KEY        = XXxxXXxxXXXXXxxx,
TWITTER_CONSUMER_SECRET     = xxxXXxxxxXXxxXXXXXxxxxxXXXXxxxxXXxxxxXX,
TWITTER_ACCESS_TOKEN        = ddddddd-xxxXXxxxxXXxxXXXXXxxxxxXXXXxxxxXXxxxxXX,
TWITTER_ACCESS_TOKEN_SECRET = XXXxxXXxxXXXXXxxxxxXXXXxxxxXXxxxxXX;
function getAuthorization(httpMethod, baseUrl, reqParams) {
    // Get acces keys
    let keysJson            = require('keys.json');
    const consumerKey       = keysJson.TWITTER_CONSUMER_KEY,
        consumerSecret      = keysJson.TWITTER_CONSUMER_SECRET,
        accessToken         = keysJson.TWITTER_ACCESS_TOKEN,
        accessTokenSecret   = keysJson.TWITTER_ACCESS_TOKEN_SECRET;
    // timestamp as unix epoch
    let timestamp  = Math.round(Date.now() / 1000);
    // nonce as base64 encoded unique random string
    let nonce      = btoa(consumerKey + ':' + timestamp);
    // generate signature from base string & signing key
    let baseString = oAuthBaseString(httpMethod, baseUrl, reqParams, consumerKey, accessToken, timestamp, nonce);
    let signingKey = oAuthSigningKey(consumerSecret, accessTokenSecret);
    let signature  = oAuthSignature(baseString, signingKey);
    // return interpolated string
    return 'OAuth '                                         +
        'oauth_consumer_key="'  + consumerKey       + '", ' +
        'oauth_nonce="'         + nonce             + '", ' +
        'oauth_signature="'     + signature         + '", ' +
        'oauth_signature_method="HMAC-SHA1", '              +
        'oauth_timestamp="'     + timestamp         + '", ' +
        'oauth_token="'         + accessToken       + '", ' +
        'oauth_version="1.0"'                               ;
}

This function can be used to generate Authorization header. Example below shows sample API call using Angular $resource

$resource(url, null, {
    get: {
        method: 'GET',
        headers: {
            'Authorization': getAuthorization(
                'GET',
                'https://api.twitter.com/1.1/users/show.json',
                { 'screen_name': 'void_imagineer'}
            )
        }
    }, options
).get({ 'screen_name': 'void_imagineer'}).$promise

Generating Signature

OAuth 1.0 signature is created by HMAC-SHA1 encryption, where base string and signing key are generated as follows:

1. Base string

function oAuthBaseString(method, url, params, key, token, timestamp, nonce) {
    return method
            + '&' + percentEncode(url)
            + '&' + percentEncode(genSortedParamStr(params, key, token, timestamp, nonce));
};

2. Signing key

function oAuthSigningKey(consumer_secret, token_secret) {
    return consumer_secret + '&' + token_secret;
};

3. Signature

function oAuthSignature(base_string, signing_key) {
    var signature = hmac_sha1(base_string, signing_key);
    return percentEncode(signature);
};

Supporting functions

// Percent encoding
function percentEncode(str) {
  return encodeURIComponent(str).replace(/[!*()']/g, (character) => {
    return '%' + character.charCodeAt(0).toString(16);
  });
};
// HMAC-SHA1 Encoding, uses jsSHA lib
var jsSHA = require('jssha');
function hmac_sha1(string, secret) {
    let shaObj = new jsSHA("SHA-1", "TEXT");
    shaObj.setHMACKey(secret, "TEXT");
    shaObj.update(string);
    let hmac = shaObj.getHMAC("B64");
    return hmac;
};
// Merge two objects
function mergeObjs(obj1, obj2) {
    for (var attr in obj2) {
        obj1[attr] = obj2[attr];
    }
    return obj1;
};
// Generate Sorted Parameter String for base string params
function genSortedParamStr(params, key, token, timestamp, nonce)  {
    // Merge oauth params & request params to single object
    let paramObj = mergeObjs(
        {
            oauth_consumer_key : key,
            oauth_nonce : nonce,
            oauth_signature_method : 'HMAC-SHA1',
            oauth_timestamp : timestamp,
            oauth_token : token,
            oauth_version : '1.0'
        },
        params
    );
    // Sort alphabetically
    let paramObjKeys = Object.keys(paramObj);
    let len = paramObjKeys.length;
    paramObjKeys.sort();
    // Interpolate to string with format as key1=val1&key2=val2&...
    let paramStr = paramObjKeys[0] + '=' + paramObj[paramObjKeys[0]];
    for (var i = 1; i < len; i++) {
        paramStr += '&' + paramObjKeys[i] + '=' + percentEncode(decodeURIComponent(paramObj[paramObjKeys[i]]));
    }
    return paramStr;
};

Pitfalls for client-only app:

My idea was to implement this app entirely on frontend. This is not feasible because of 2 reasons:

  • Can’t ensure security for access keys: Storing these keys without revealing to browser console is impossible.
    Hard fix: make the user to connect their own access keys by enabling Twitter authentication.

  • Browser blocking CORS: Twitter API response doesn’t have Access-Control-Allow-Origin header set, because of that browsesrs will not accept responses.
    Quick fix: tunnel requests though a corsproxy server.

You can check out the code for Twick (Angular Twitter data fetch app) in Github.

Twick Code at Github

References:

blog comments powered by Disqus