DEV Community

Cover image for Conquering Twitter OAuth Authorization in a Rails/React App
Liz Laffitte
Liz Laffitte

Posted on

Conquering Twitter OAuth Authorization in a Rails/React App

Cover Photo by Brett Jordan from Pexels

This is a quick walkthrough of making Twitter API requests that require OAuth authorization, in a Rails/React app. You will need a Twitter developer account, an app with read and write permissions registered within your account, and your application’s API key and API key secret. You’ll also need to have added a callback URL for your application in your developer portal. I suggest app.com/auth/twitter/callback.

Please note that Twitter calls their client credentials several different things, but I’ll be using API key and API secret to keep it simple.

If you’ve ever built a React application with a separate Rails API backend, you’ve likely used the omniauth and omniauth-twitter gems to handle authorization for various social media platforms. However, if your application, like my social media assistant app, does not have separate front and backends (i.e. you’re not using views at all), you won’t be able to (easily) use the omniauth gem.

Instead, we’re going to use oauth and httparty.

The Twitter 3-legged OAuth flow is made up of three steps:

  1. POST oauth/request_token
  2. If successful, the request response will return a token and a token secret
  3. GET oauth/authorize
  4. Your application will send a GET request to https://api.twitter.com/oauth/authorize
  5. The request will include the previously returned token as a param.
  6. Your app’s user will be asked to authorize your app
  7. If successful, your user will be redirected to the callback URL specified in your developer portal
  8. The request response will include a oauth_token and a oauth_verifier
  9. POST oauth/access_token
  10. Your app will send a POST request to https://api.twitter.com/oauth/access_token
  11. The request will include the previously returned oauth_token and oauth_verifier as params.

First, in our application’s Gemfile, we’ll add oauth and httparty as dependencies.

Gemfile

gem 'httparty'
gem 'oauth'
Enter fullscreen mode Exit fullscreen mode

Make sure to run $bundle install.

Next, we’ll save our API key and API key secret as environmental variables in a untracked .env file.

Never, ever, save API keys or other sensitive information into a file that’s being tracked by a version control system; or upload it somewhere public where it can be accessed (e.g. GitHub).

.env

KEY=THISISOBVIOUSLYNOTMYAPIKEYREPLACEWITHYOURS
SECRET=AGAINTHISISJUSTANEXAMPLE
Enter fullscreen mode Exit fullscreen mode

The next step is connecting your callback URL to the sessions controller create action.

/config/routes.rb

  get '/auth/:provider/callback', to: "sessions#create"
Enter fullscreen mode Exit fullscreen mode

Now we’re set up and ready to start the 3-legged OAuth flow.

Step 1: POST oauth/request_token

We’re going to be making our API calls from a controller that exclusively handle any social media API calls.

app/controllers/social_controller.rb

require 'pry'
require 'oauth'
class SocialController < ApplicationController
    def create
        ckey = ENV['KEY']
        csecret = ENV['SECRET']
        consumer = OAuth::Consumer.new(ckey,csecret,
            :site => 'https://api.twitter.com',
            :authorize_path => '/oauth/authenticate',
            :debug_output => false)
        callback_url = "http://127.0.0.1:3000/auth/twitter/callback"
        request_token = consumer.get_request_token(:oauth_callback => callback_url)
        token = request_token.token
        token_secret = request_token.secret
        confirmed = request_token.params["oauth_callback_confirmed"]
        if confirmed === "true"
            redirect_to "https://api.twitter.com/oauth/authorize?oauth_token=#{token}"
        else
            redirect_to "/"
        end
    end
end
Enter fullscreen mode Exit fullscreen mode

In the method, first we’re saving our API key and API key secret from our .env file to local variables.

def req_token
  ckey = ENV['KEY']
   csecret = ENV['SECRET']
…
end
Enter fullscreen mode Exit fullscreen mode

Then we are creating a new consumer instance, passing it the API key and secret, the site we’re making the API request to, the authorize path and setting debug_output to false.

def req_token
…
        consumer = OAuth::Consumer.new(ckey,csecret,
            :site => 'https://api.twitter.com',
            :authorize_path => '/oauth/authenticate',
            :debug_output => false)
…
Enter fullscreen mode Exit fullscreen mode

Next we save the callback URL into a local variable. We then make the POST request, by calling get_request_token on our consumer instance, passing in the callback variable and saving the response as request_token (step 1 a-b). We then use that response to save the returned token and token secret (step 1 c).

Step 2: GET oauth/authorize

def req_token
…
callback_url = "http://127.0.0.1:3000/auth/twitter/callback"
request_token = consumer.get_request_token(:oauth_callback => callback_url)
token = request_token.token
 token_secret = request_token.secret
…
end
Enter fullscreen mode Exit fullscreen mode

To make sure our request was successful, we’re checking to see if request_token contains oauth_callback_confirmed. If so, we’re redirecting to https://api.twitter.com/oauth/authorize (step 2 a), adding the token we just saved to the URL as a param (step 2 b).

def req_token
…
confirmed = request_token.params["oauth_callback_confirmed"]
        if confirmed === "true"
            redirect_to "https://api.twitter.com/oauth/authorize?oauth_token=#{token}"
        else
            redirect_to "/"
        end
    end
Enter fullscreen mode Exit fullscreen mode

This is the point in the OAuth flow when the users will be redirected and asked to authorize your application. If they do, they’ll be redirected to yourapp.com/auth/twitter/callback which we connected to sessions#create.

Step 3: POST oauth/access_token

For example purposes, I’ve dumped all the rest of the flow into sessions#create.


def create
        if params[:oauth_token] && params[:oauth_verifier]
            oauth_token = params["oauth_token"]
            oauth_verifier = params["oauth_verifier"]
            baseUrl = 'https://api.twitter.com/oauth/access_token'
            response = HTTParty.post(baseUrl + "?oauth_token=#{oauth_token}&oauth_verifier=#{oauth_verifier}" )
            @access_token = response.split("&")[0].split("=")[1]
            @access_secret = response.split("&")[1].split("=")[1]
            @user_id = response.split("&")[2].split("=")[1]
            @user_sn = response.split("&")[3].split("=")[1]
            user = User.find_by(username: @user_sn)
            if user
                session[:user_id] = user.id
                render json: UserSerializer.new(user)
            else
                new_user_info = get_user(@user_sn)
                 new_user = User.new(username: new_user_info[“name”], password: SecureRandom.hex, uid: @user_id )
                if @user.save
                   log_in(@user)
                  else
                  render :new
                end
                render json: new_user_info
…
            end
        end
    end
Enter fullscreen mode Exit fullscreen mode

If the params promised in step 2e were returned, we’ll use HTTParty to make a post request, passing those params to the base URL.

That’s it! After you get the basics down, you’ll be able to connect the API calls to actions on your frontend.

Top comments (2)

Collapse
 
harrisreynolds profile image
Harris Reynolds • Edited

I did a bit of refactoring on the parsing of the response just to avoid relying on the order of the response:

response_data = response.split("&")
@access_token = parse_token_data(response_data, 'oauth_token=' )
@access_secret = parse_token_data(response_data, 'oauth_token_secret=' )
@twitter_user_id = parse_token_data(response_data, 'user_id=' )
@screen_name = parse_token_data(response_data, 'screen_name=' )
Enter fullscreen mode Exit fullscreen mode

With a new method:

def parse_token_data(tokens, token_name)
  tokens.each do |token|
    return token.split("=")[1] if token.index(token_name).present?
  end
end
Enter fullscreen mode Exit fullscreen mode

It will be a bit slower since it iterates over every part of the response, but it does ensure the correct pieces are connected instead of relying on the order.

Collapse
 
harrisreynolds profile image
Harris Reynolds

This was a legit post. It was blowing my mind how painful it was to do this since most Oauth implementations are more simple (like Stripe and GitHub etc).

But if you know the steps its not too bad! Thank you!

--harris