PIN-Based OAuth in Twitter

This week I’ve had a bit of a play with creating some apps that run in the terminal using Node.js. Partly inspired by my previous blog where I created a console tool to output a days commit messages in their respected branches.

The goal of this blog is to create a console application from which I can send tweets. But to do that, I’m going to need some way to auth.

Pin-based OAuth

For a console application, we need something a little different to the traditional 3-legged OAuth as we don’t have a callback URL. So we will set the value for oauth_callback to oob, which stands for “out of band” as to say, non-traditional. Once the user grants permissions to your application through a browser, Twitter will provide them with a 7-digit PIN, which they will copy and paste into your application. Your application will then plug that number into the final POST oauth/access_token request.

This should all make a lot more sense as we go through it.

Testing with postman

I’m assuming you have a Twitter application, if not just go to the Twitter app dashboard and create one.

Next, go to Keys and Tokens and generate some consumer keys, and store them somewhere.

1. Obtain request token

Create POST https://api.twitter.com/oauth/request_token request in Postman with the Authorization type OAuth 1.0:

Make sure to fill in your Consumer Key and your Consumer Secret with the keys/secrets, we just generated in Twitter. And importantly, add oob as the Callback URL

Running that should give you something like:

oauth_token=iZ90iwAAAAABTB3vABBBABe4xmP2U&oauth_token_secret=L1ASPRVlTNBfAipyphRegZIDQrY7Xlwi&oauth_callback_confirmed=true

We’re gonna need the oauth_token and oauth_token_secret.

2. Redirect the user

Next, open up the page https://api.twitter.com/oauth/authorize?oauth_token=YOUR_OAUTH_TOKEN_HERE

Which should look something like this:

Hit Authorize App and you’ll be provided with a code:

We’re gonna need that number for the next postman request.

3. Get the user access token

We need the user access token so we can post tweets on their behalf. Create a new POST https://api.twitter.com/oauth/access_token?oauth_token=OAUTH_TOKEN_HERE&oauth_verifier=PIN_HERE

The response should look something like this:

oauth_token=1024229588069765120-VsTP0ELWFAY0H5LN2nbTVEwkXcpWkp&oauth_token_secret=U3uhlwQrDSLR9SLZtDKRofNcUcwXMtY6zAqJWvpm9PC7&user_id=1124229588069765120&screen_name=luke_garrigan

Now we have the auth token and secret we can send tweets on behalf of the user!

4. Sending a tweet

Create a POST https://api.twitter.com/1.1/statuses/update.json?status=TWEET_HERE

Plug in your Consumer Key and Consumer Secret (The ones generated in your Twitter app) and set the Access token and Token Secret for the user (The ones we just go from the previous request).

Et voila:

Pin OAuth with Node.js – To send a tweet

It’s all well and good doing it in Postman but how do we transfer that to code? Quite easily actually. I’ve created a GitHub repo just for the Pin OAuth.

Installing the dependencies

First of all, let’s install OAuth to make our lives a hell of a lot easier:

npm install oauth

open so that we can open the browser for the user to auth:

npm install open

readline so we can read in the user’s 7-digit pin:

npm install readline

dotenv so we don’t commit our keys to version control!

Obtain request token

Let’s import all of the modules we’re gonna need:

const oauth = require('oauth');
const readline = require('readline');
const open = require('open');
require('dotenv').config();

Next, let’s use the OAuth library to create the OAuth object which takes our keys generated in Twitter:

const consumer = new oauth.OAuth(
    "https://twitter.com/oauth/request_token", 
    "https://twitter.com/oauth/access_token",
    process.env.TWITTER_API_KEY, 
    process.env.TWITTER_API_SECRET, 
    "1.0A", "oob", "HMAC-SHA1");

Note: You’ll need to create a .env file that’ll contain your keys, something like:

TWITTER_API_KEY=NzDLSiItgS0aBhlb9AmF2Mddp
TWITTER_API_SECRET=QTRosftjdkjN5wNufJioZyfUIbQlL4KhgWbF1k81Py5vrPmD6H

Let’s make the request to get the request token, and once we’ve got it let’s open the url for the user to auth:

async function performAuth() {
    const authRequest = await getAuthRequestToken();
    console.log(authRequest);
}

async function getAuthRequestToken() {
  return new Promise((resolve, reject) => {
    consumer.getOAuthRequestToken(async (error, oauthToken, oauthTokenSecret, results) => {
      if (error) {
        reject(error);
      } else {
        await open(`https://twitter.com/oauth/authorize?oauth_token=${oauthToken}`);
        resolve({
          oauthToken,
          oauthTokenSecret
        });
      }
    });
  });
}

Running this should open up Twitter:

Accept the user’s pin

Next, we need to wait for the user to enter their pin, add the following to the global scope, we’ll probably want this later on.

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
    });

And let’s add some code to wait for the users to input:

async function getUserPin() {
    return new Promise((resolve) => {
      rl.question('Enter your code...\n', (pin) => {
        console.log(`you entered ${pin}`);
        resolve(pin);
      });
    })
  }

And of course, call it:

async function performAuth() {
    const authRequest = await getAuthRequestToken();
    const pin = await getUserPin();
}

So now when you run you should have something like this:

Get User Access Token

Add the following method which takes the oauthToken, oauthTokenSecret and the pin we just received:

async function getAuthAccessToken(oauthToken, oauthTokenSecret, pin) {
  return new Promise((resolve, reject) => {
    consumer.getOAuthAccessToken(oauthToken, oauthTokenSecret, pin, (error, oauthAccessToken, oauthAccessTokenSecret, results) => {
      if (error) {
        reject(error);
      } else {
        console.log('Successfully authorised with twitter');
        resolve({
          oauthAccessToken,
          oauthAccessTokenSecret
        })
      }
    })
  });
}

And call it:

async function performAuth() {
    const authRequest = await getAuthRequestToken();
    const pin = await getUserPin();
    const authAccess = await getAuthAccessToken(authRequest.oauthToken, authRequest.oauthTokenSecret, pin);
}

So running this should output:

Sending a tweet

Let’s add two new functions, one to gather the user input so they can choose what to tweet and another to send the tweet:

  async function getTweetToSend() {
    return new Promise((resolve) => {
      rl.question('Enter tweet to send\n', (tweet) => {
        resolve(tweet);
      });
    })
  }

  async function sendTweet(oauthAccessToken, oauthAccessTokenSecret, tweet) {
    return new Promise((resolve, reject) => {
      consumer.post(`https://api.twitter.com/1.1/statuses/update.json?status=${tweet}`, oauthAccessToken, oauthAccessTokenSecret, {} ,function (error, data, response) {
        if (error) {
          console.log(error)
          reject(error);
        } else {
          console.log(`You sent tweet ${tweet}`)
          resolve(data);
        }
      })
    })
  }

And call them:

async function performAuth() {
    const authRequest = await getAuthRequestToken();
    const pin = await getUserPin();
    const authAccess = await getAuthAccessToken(authRequest.oauthToken, authRequest.oauthTokenSecret, pin);
    const tweet = await getTweetToSend();
    await sendTweet(authAccess.oauthAccessToken, authAccess.oauthAccessTokenSecret, tweet);
}

Jobs a good’un.

All the code should look something like the following:

const oauth = require('oauth');
const readline = require('readline');
const open = require('open');
require('dotenv').config();

const consumer = new oauth.OAuth(
    "https://twitter.com/oauth/request_token",
    "https://twitter.com/oauth/access_token",
    process.env.TWITTER_API_KEY,
    process.env.TWITTER_API_SECRET,
    "1.0A", "oob", "HMAC-SHA1");

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
    });


performAuth();

async function performAuth() {
    const authRequest = await getAuthRequestToken();
    const pin = await getUserPin();
    const authAccess = await getAuthAccessToken(authRequest.oauthToken, authRequest.oauthTokenSecret, pin);
    const tweet = await getTweetToSend();
    await sendTweet(authAccess.oauthAccessToken, authAccess.oauthAccessTokenSecret, tweet);
}

async function getAuthRequestToken() {
  return new Promise((resolve, reject) => {
    consumer.getOAuthRequestToken(async (error, oauthToken, oauthTokenSecret, results) => {
      if (error) {
        reject(error);
      } else {
        await open(`https://twitter.com/oauth/authorize?oauth_token=${oauthToken}`);
        resolve({
          oauthToken,
          oauthTokenSecret
        });
      }
    });
  });
}

async function getUserPin() {
    return new Promise((resolve) => {
      rl.question('Enter your code...\n', (pin) => {
        console.log(`you entered ${pin}`);
        resolve(pin);
      });
    })
  }


async function getAuthAccessToken(oauthToken, oauthTokenSecret, pin) {
    return new Promise((resolve, reject) => {
      consumer.getOAuthAccessToken(oauthToken, oauthTokenSecret, pin, (error, oauthAccessToken, oauthAccessTokenSecret, results) => {
        if (error) {
          reject(error);
        } else {
          console.log('Successfully authorised with twitter');
          resolve({
            oauthAccessToken,
            oauthAccessTokenSecret
          })
        }
      })
    });
  }

  async function getTweetToSend() {
    return new Promise((resolve) => {
      rl.question('Enter tweet to send\n', (tweet) => {
        resolve(tweet);
      });
    })
  }

  async function sendTweet(oauthAccessToken, oauthAccessTokenSecret, tweet) {
    return new Promise((resolve, reject) => {
      consumer.post(`https://api.twitter.com/1.1/statuses/update.json?status=${tweet}`, oauthAccessToken, oauthAccessTokenSecret, {} ,function (error, data, response) {
        if (error) {
          console.log(error)
          reject(error);
        } else {
          console.log(`You sent tweet ${tweet}`)
          resolve(data);
        }
      })
    })
  }

Conclusion

PIN-Based OAuth is a great way to authorise with command-line applications is it is relatively simple. If you know how to use OAuth in the traditional 3-legged approach, you’ll find the transition to PIN-Based OAuth very simple.

Remember, all the code can be found on my GitHub.

If you liked this blog then please sign up for my newsletter and join an awesome community!!

Leave a Reply