Omniauth with Ruby on Rails, Devise, and Twitter

12.12.2014

In short, omniauth lets your users log in to your website through a third party like Twitter or Facebook. In this post, I’ll show you how to set up Omniauth with Ruby on Rails, Devise, and Twitter.

Prerequisites:

  1. You have Devise already installed with User model on a Rails app
  2. You have a Twitter account

Step 1: Add omniauth-twitter to Gemfile and run bundle

Gemfile

gem 'omniauth-twitter'
$ bundle install

Step 2: Migrations

$ rails g migration AddColumnsToUsers provider uid twitter_handle
$ rake db:migrate

Step 3: Omniauth Callbacks Controller

You can either create this file or use the $ rails g devise:controllers command to generate it.

/app/controllers/users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def all
    user = User.from_omniauth(request.env["omniauth.auth"])
    if user.persisted?
      flash.notice = "Signed in!"
      sign_in_and_redirect user
    else
      session["devise.user_attributes"] = user.attributes
      redirect_to new_user_registration_url
    end
  end
  alias_method :twitter, :all
end

Step 4: Users Model

/app/models/user.rb

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  devise :omniauthable, omniauth_providers: [:twitter]

  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.twitter_handle = auth.info.nickname
      user.email = auth.info.email
      user.password = Devise.friendly_token[0,20]
    end
  end

  def self.new_with_session(params, session)
    if session["devise.user_attributes"]
      new(session["devise.user_attributes"], without_protection: true) do |user|
        user.attributes = params
        user.valid?
      end
    else
      super
    end
  end

  def password_required?
    super && provider.blank?
  end

  def email_required?
    super && provider.blank?
  end

  def update_with_password(params, *options)
    if encrypted_password.blank?
      update_attributes(params, *options)
    else
      super
    end
  end
end

Step 5: Create App on apps.twitter.com

  1. Go to apps.twitter.com
  2. Click “Create New App”
  3. Fill in the required fields
  4. Also fill in the callback URL. This is very important as the log in will not work if you leave this blank. If you don’t have a domain yet, you can use localhost:3000
http://photocritic.herokuapp.com/users/auth/twitter

or

http://localhost:3000/users/auth/twitter

Step 6: API ID and Secret

Add the newly created API ID and API Secret to devise.rb. config/initializers/devise.rb

config.omniauth :twitter, 'API_ID', 'API_SECRET'

Step 7: Edit the routes file

config/routes.rb

devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

Step 8: Sign Up and Log In Links with Logged In Indicator

app/views/layouts/application.html.erb

<ul>
  <% if user_signed_in? %>
    <li><%= link_to "Settings", edit_user_registration_path, :data => {:bypass => true} %></li>
    <li><%= link_to "Sign Out", destroy_user_session_path, :method => :delete, :data => {:bypass => true} %></li>
  <% else %>
    <li><%= link_to "Login", new_user_session_path %></li>
    <li><%= link_to "Sign in with Twitter", user_omniauth_authorize_path(:twitter) %></li>
    <li><%= link_to "Sign Up", new_registration_path(:user) %></li>
  <% end %>
</ul>

<% if user_signed_in? %>
  <% if current_user.provider == "twitter" %>
    <p class="text-right">Signed in through twitter as <%= current_user.twitter_handle %></p>
  <% else %>
    <p class="text-right">Signed in as <%= current_user.email %></p>
  <% end %>
<% end %>

Step 9: Change User Table to Accept Null Values for Email Column

We need to do this because Twitter doesn’t send an email back which will cause a database error when creating new users.

$ rails g migration ChangeUserEmailToNullTrue
class ChangeUsersEmailToNullTrue < ActiveRecord::Migration
  def change
    change_column_null :users, :email, true
  end
end
$ rake db:migrate

Conclusion

So what exactly does all that code do? Here is a quick rundown of the omniauth process.

  1. User clicks Sign in with Twitter
  2. User gets redirected to Twitter log in page
  3. User enters credentials
  4. If valid, Twitter sends a callback to the URL we specified earlier with a payload containing user info (including the Twitter handle)
  5. In the self.omni_auth method, we find or create the user
  6. There is additional logic in the User model to check if omniauth provider is nil. If so, it means that the user is signing up through our system and we require a password. Otherwise, skip the validation if there is a provider.

That’s it.