« Back

🆔

Twitter auth in Rails (2/2)

on programming
last updated 2/7/21

In part 1, we finished up by tweaking settings in the Twitter developer portal. Our homepage link and Twitter-hosted login don't work quite yet; we need to implement OmniAuth first.


Configuring OmniAuth

OmniAuth is an authentication system that serves as middleware.

It sits between our application and external providers, enabling users to log into our application with an existing account on Twitter, Google, or other popular platforms.

The omniauth & omniauth-twitter gems are required for this, and I recommend adding the twitter gem (more on this here) to simplify interactions with the Twitter API after logging in.


# liked/Gemfile
gem 'omniauth'
gem 'omniauth-twitter'
gem 'twitter'
...
        

So that our homepage link actually redirects to a Twitter login page, we'll add an omniauth.rb file in config/initializers with the following block:


# liked/config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET']
end
        

# liked/app/views/users/show.erb
<%= button_to "/auth/twitter", method: :get %>
        

Upon attempted logins, Twitter's servers respond with information for the user's Twitter account via a GET request to the callback URL we set in our developer portal (at the end of part 1).

We'll add two callback routes to handle responses for both successful and unsuccessful attempts.


# liked/config/routes.rb
Rails.application.routes.draw do
  root 'users#home'
  get '/auth/failure' => '/'
  get '/auth/twitter/callback' => 'sessions#create'
end
        

Unsuccessful logins will hit that 2nd route and direct users back to our root route: the homepage.

The 3rd route will handle responses for successful logins. GET requests to the callback URL auth/twitter/callback will be matched to the sessions controller's create action.

For context, take a look at an example response from the omniauth-twitter documentation.


Handling the Twitter API response

We can process responses in the sessions#create controller action or as I've done below, move that logic into a model as a dedicated method .

The sessions#create action first invokes a User class method, passing it the response. To assist in parsing this response hash, I also added a simple helper method.


# liked/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

# GET /auth/twitter/callback
  def create
    user = User.from_omniauth(auth)
  end

  private

  # helper method to assist in parsing omniauth responses
  def auth
    request.env['omniauth.auth']
  end
end
        

In this User class method, we need to find the existing account associated with this Twitter account or create a new account altogether. We'll do so with the value for the uid property, a unique identifier for every Twitter account, in the response.

#find_or_create_by is a handy ActiveRecord method for this sort of scenario. When a record is found with the given uid attribute, the block is skipped and that record is returned.

When a record is not found — meaning the authenticating user has not logged into our application with that Twitter account before — the block is invoked to create and return a new user record.


# liked/app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
  validates :handle, presence: true, uniqueness: true
  validates :token, presence: true, uniqueness: true
  validates :secret, presence: true, uniqueness: true

# finds existing user or creates new user
  def self.from_omniauth(auth)
    self.find_or_create_by(uid: auth['uid']) do |u|
      u.name = auth[:info][:name]
      u.handle = auth[:info][:nickname]
      u.token = auth[:credentials][:token]
      u.secret = auth[:credentials][:secret]
    end
  end
end
        

The access token and secret can be used to make requests to the Twitter API retrieving the user's follows, likes, and so on. Like uid, they're unique to each Twitter account and do not expire.

So .from_omniauth returns a user record for the current user. We'll then store attributes for that record in the session hash (more on this here) so the user doesn't have to log in again.


# liked/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

# GET /auth/twitter/callback
  def create
    user = User.from_omniauth(auth)
    session[:user_id] = user.id
    session[:token] = user.token
    session[:secret] = user.secret
    redirect_to user_path(user)
  end

  private

  # helper method to simplify parsing omniauth response
  def auth
    request.env['omniauth.auth']
  end
end
        

Where to redirect the user after this is completely up to you. At a minimum, you'll probably need another route, controller action, and view.


Conclusion

If you've read this far, I have a pair of final recommendations:

I suggest taking care of that first piece immediately after adding the login button, route, and controller action. Here's an example:


# liked/config/routes.rb
Rails.application.routes.draw do
  root 'users#home'
  get '/auth/failure' => '/'
  get '/auth/twitter/callback' => 'sessions#create'
  post '/logout' => 'sessions#destroy' # logout route
  resources :users, only: [:show]
end
        

# liked/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

# GET /auth/twitter/callback
  def create
    user = User.from_omniauth(auth)
    session[:user_id] = user.id
    session[:token] = user.token
    session[:secret] = user.secret
    redirect_to user_path(user)
  end

# POST /logout - logout controller action
# clears the session, 'logging out' the user and redirecting them
  def destroy
    session.clear
    redirect_to root_path
  end

  private

  def auth
    request.env['omniauth.auth']
  end
end
        

  # liked/app/views/layouts/application.html.erb - layout with logout link
  ...
  <%= link_to "Sign out", logout_path, method: :post %>
  ...
        

As for those helper methods, here are some examples. They can be used to simplify controller logic, such as filters for controller actions.

I'm happy to try and answer any questions you might have related to implementing an OmniAuth strategy for Twitter in your Rails app (either here or in part 1). Please just email me.

I've also included some links below that helped me with this process.


INSPIRATION