/ Authentication / Login with Multiple Providers + Devise via Omniauth

Setting up authentication with multiple providers (Facebook, Twitter, Linkedin, Github, Google+) is a little bit tricky for me when I was started to implement it on my applications and projects. There are numerous tutorials out there, but they were not explaining it thoroughly enough. Thus, as I am building the Quickrails starter kit, I am documenting this tricky bit as well for those who needed it. So let’s start.

1. Creating a New App for Each Providers

In order for us to implement authentication via social media apps like Facebook, Twitter, Linkedin, Github and Google+, we need to create the new app for each of them. We need to create this so that we could have the authentication tokens that will then be used to connect our app to those social media sites. Click the links below to see to create & configure the new app for each of them:

Create Facebook App for Omniauth

Create Twitter App for Omniauth

Create Linkedin App for Omniauth

Create Github App for Omniauth

Create Google App for Omniauth

 

2. Installing all the Omniauth gems

Go to your gemfile, and put these:

# Authentication
gem 'devise'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-linkedin'
gem 'omniauth-github'
gem "omniauth-google-oauth2"

and then run bundle install

 

To see more details about each gems, click the links below:

Facebook: https://github.com/mkdynamic/omniauth-facebook

Twitter: https://github.com/arunagw/omniauth-twitter

Linkedin: https://github.com/skorks/omniauth-linkedin

Github: https://github.com/intridea/omniauth-github

Google: https://github.com/zquestz/omniauth-google-oauth2 

 

3. Edit devise.rb

app/config/initializers/devise.rb

config.omniauth :facebook, 'APP_ID', 'APP_SECRET', :scope => 'email', info_fields: 'email, first_name, last_name'
config.omniauth :twitter, 'APP_ID', 'APP_SECRET'
config.omniauth :linkedin, 'APP_ID', 'APP_SECRET'
config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: "user:email"
config.omniauth :google_oauth2, 'APP_ID', 'APP_SECRET'

Replace APP_ID and APP_SECRET with your’s APP_ID and APP_SECRET that you got from step 1.

Note: You might want to store your APP_ID and APP_SECRET  as the ENV variables especially when you push your codes to production. For more info on how to do this, see here: https://goo.gl/xBMdmi

 

4. Create OmniauthCallbacks Controller

This controller will be used to as a callback when the user has been authorized by any provider.

app/controllers/omniauth_callbacks_controller.rb

class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def all
    
    user = User.from_omniauth(request.env["omniauth.auth"])

    if user.persisted? 
      session[:user_id] = user.id
      sign_in_and_redirect user, notice: "Signed in!"
    else

      # Devise allow us to save the attributes eventhough 
      # we havent create the user account yet
      session["devise.user_attributes"] = user.attributes

      # Because Twitter doesn't provide user's email, it would be nice if we force user to enter it
      # manually on the registration page before we create their account.
      # Here we pass the callback parameter so that we could use it to edit the registration page.
      redirect_to new_user_registration_url(:callback => "twitter")

    end

  end
  
  alias_method :facebook, :all
  alias_method :twitter, :all
  alias_method :linkedin, :all
  alias_method :github, :all
  alias_method :google_oauth2, :all
end

 

5. Edit User model

class User < ActiveRecord::Base

  devise :database_authenticatable, :registerable, :omniauthable,
         :recoverable, :rememberable, :trackable, :validatable

  def self.from_omniauth(auth, signed_in_resource = nil)

  user = User.where(provider: auth.provider, uid: auth.uid).first

  if user.present?
    user
  else

    # Check wether theres is already a user with the same 
    # email address
    user_with_email = User.find_by_email(auth.info.email)

    if user_with_email.present?
      user = user_with_email
    else
        user = User.new

        if auth.provider == "facebook"
        
        	user.provider = auth.provider
        user.uid = auth.uid
          user.oauth_token = auth.credentials.token
        	
        	user.first_name = auth.extra.raw_info.first_name
        	user.last_name = auth.extra.raw_info.last_name
          user.email = auth.extra.raw_info.email

          # Facebook's token doesn't last forever
        user.oauth_expires_at = Time.at(auth.credentials.expires_at)

        user.save

      elsif auth.provider == "linkedin" 
      
        user.provider = auth.provider
        user.uid = auth.uid
          user.oauth_token = auth.credentials.token
          
          user.first_name = auth.info.first_name
          user.last_name = auth.info.last_name
          user.email = auth.info.email
      
        user.save

      elsif auth.provider == "twitter" 
        
        	user.provider = auth.provider
        user.uid = auth.uid
          user.oauth_token = auth.credentials.token
        	
        	user.oauth_user_name = auth.extra.raw_info.name
         	
         	elsif auth.provider == "github" 		    
        	
        	user.provider = auth["provider"]
      			user.uid = auth["uid"]
 
      			user.oauth_user_name = auth["info"]["name"]	      		
 				user.email = auth["info"]["email"]

 				user.save

         	elsif auth.provider == "google_oauth2"
         		
         		user.provider = auth.provider
        user.uid = auth.uid
          user.oauth_token = auth.credentials.token
        	
        	user.first_name = auth.info.first_name
        	user.last_name = auth.info.last_name
          user.email = auth.info.email

          # Google's token doesn't last forever
        user.oauth_expires_at = Time.at(auth.credentials.expires_at)

        user.save

         	end

    end    
  end

  return user

  end

  # For Twitter (save the session eventhough we redirect user to registration page first)
  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  

  # For Twitter (disable password validation)
  def password_required?
    super && provider.blank?
  end        

end



5. Edit Routing

app/config/routes.rb

Update your users route to this:

devise_for :users, :controllers => { sessions: 'sessions', registrations: 'registrations', omniauth_callbacks: "omniauth_callbacks", confirmations: "confirmations" }
as :user do
   get 'login' => 'sessions#new', :as => "login"
   get 'signup' => 'registrations#new', :as => "signup"  
   get 'signout' => 'devise/sessions#destroy', :as => "signout"
end

 

No issues for this post.

  • Jeremy Whitman

    Looks like a pretty good tutorial. I’ve been having trouble with this.

    You mIght want to put in a note or a link about environmental variables and a that the app_id and app_secret would be better off stored in ENV variables for any app that potentially placed on github or a similar public site.

    • Rizal Yusoff

      Glad that I could help 🙂

      Ah yes, I will put that later on, thanks for your suggestion!

    • Glad that I could help 🙂

      Ah yes, I will put this later on, thanks for the suggestion!

  • Kasper Sky

    There is no mention of routing

    • Hi, I have added the routing for this tutorial 🙂

  • fatkodima

    OMG. :facepalm: for code quality

    • Yes i know. I coded them that way to give better readability and easier understanding for those who are new to ruby on rails

by ryzalyusoff