24

I have spent days watching the RailsCasts on devise and omniauth and then going through related tutorials for setting up an authentication system that uses these gems. I think the RailsCasts are out of date and trying to patch the gaps with other tutorials is creating all kinds of issues.

Please can anyone suggest a current tutorial that I can use as a basis for implementing this system. I have separate user and authentications models (with users having many authentications).

I'd really like to use devise and omniauth (with CanCan for abilities) on rails 4 but am tearing my hair out in trying to find a basic setup (using psql as a database).

Raj
  • 22,346
  • 14
  • 99
  • 142
Mel
  • 2,481
  • 26
  • 113
  • 273
  • official docs are the way to go – Raj Jan 21 '14 at 05:24
  • I'm having this same issue... Any solutions? –  Feb 20 '14 at 21:08
  • I found this: https://github.com/mohitjain/social-login-in-rails, but I don't think it can solve the issue of linking users/authentications from Twitter. Twitter doesn't share the email and his User model uses the email to look for previous records. Hmm... hopefully someone can find a good solution to all this. – robertwbradford Mar 01 '14 at 03:30
  • The Railscasts are a little outdated (the revised ones are a little better) but you should still be able to implement Devise + OmniAuth just using those. What is the problem exactly? – Ashitaka Mar 02 '14 at 19:18

3 Answers3

35

To use Devise with multiple auth providers you need 1 more model - Authorization

#authorization.rb

# == Schema Information
#
# Table name: authorizations
#
#  id           :integer          not null, primary key
#  user_id      :integer
#  provider     :string(255)
#  uid          :string(255)
#  token        :string(255)
#  secret       :string(255)
#  created_at   :datetime
#  updated_at   :datetime
#  profile_page :string(255)
#

class Authorization < ActiveRecord::Base
  belongs_to :user
end

There is code for User model

#user.rb

SOCIALS = {
  facebook: 'Facebook',
  google_oauth2: 'Google',
  linkedin: 'Linkedin'
}


has_many :authorizations

def self.from_omniauth(auth, current_user)
  authorization = Authorization.where(:provider => auth.provider, :uid => auth.uid.to_s, 
                                      :token => auth.credentials.token, 
                                      :secret => auth.credentials.secret).first_or_initialize
  authorization.profile_page = auth.info.urls.first.last unless authorization.persisted?
  if authorization.user.blank?
    user = current_user.nil? ? User.where('email = ?', auth['info']['email']).first : current_user
    if user.blank?
      user = User.new
      user.skip_confirmation!
      user.password = Devise.friendly_token[0, 20]
      user.fetch_details(auth)
      user.save
    end
    authorization.user = user
    authorization.save
  end
  authorization.user
end

def fetch_details(auth)
  self.name = auth.info.name
  self.email = auth.info.email
  self.photo = URI.parse(auth.info.image)
end

And at the end you need to override methods for Devise controller

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def all
    user = User.from_omniauth(env['omniauth.auth'], current_user)
    if user.persisted?
      sign_in user
      flash[:notice] = t('devise.omniauth_callbacks.success', :kind => User::SOCIALS[params[:action].to_sym])
      if user.sign_in_count == 1
        redirect_to first_login_path
      else
        redirect_to cabinet_path
      end
    else
      session['devise.user_attributes'] = user.attributes
      redirect_to new_user_registration_url
    end
  end

  User::SOCIALS.each do |k, _|
    alias_method k, :all
  end
end

If you need some for providers, e.g. Twitter you need to override twitter method, because it doesn't provide user email and you should save it's credentails in some other way.

Also you should make some changes in routes

devise_for :users,
           :controllers => {
             :omniauth_callbacks => 'users/omniauth_callbacks',
           }
Alex Tonkonozhenko
  • 1,514
  • 12
  • 26
  • 8
    Thanks for sharing the code! Just a quick note that authorization is distinct from authentication, omniauth's concern is user authentication(who are you), whereas gems like pundit and cancan deal with authorization(We know who you are, here's what you can and cannot do). Just to clarify things for newcomers to programming. – Ryan.lay May 28 '14 at 05:05
  • 2
    Thank you so very much for this. It's actually really hard to find a complete, working solution that's up to date. – ahnbizcad Sep 20 '14 at 01:35
  • What does nesting omniauth callbacks under `users` do, and why is it necessary? – ahnbizcad Sep 24 '14 at 07:49
  • 1
    It's not necessary, but often in my projects there are a lot of changes at devise controllers so I think it's better to store them at separate folder. – Alex Tonkonozhenko Sep 24 '14 at 19:45
  • What if I have my local login also. It gives the error that email has already been taken, if I have the same email id for the local login – inquisitive Nov 21 '14 at 05:25
  • Shall it be called **Identity** instead of **Authorization** for the sake of semantics and to avoid possible confusion with role based authorization that might be placed on top later? Also [this link](https://github.com/intridea/omniauth/wiki/Managing-Multiple-Providers) might be relevant (although without devise) – mlt Jun 09 '16 at 22:43
  • 1
    @mlt I like name `Identity`. Also you can call it `Authentication`. It's better than `Authorization`. – Alex Tonkonozhenko Jun 10 '16 at 08:39
3

in you initializers try doing something like this

#config/initializers/devise.rb
  #provider1
  config.omniauth :facebook, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_SECRET'],
    :site => 'https://graph.facebook.com/',
    :authorize_path => '/oauth/authorize',
    :access_token_path => '/oauth/access_token',
    :scope => 'email, user_birthday, read_stream, read_friendlists, read_insights, read_mailbox, read_requests, xmpp_login, user_online_presence, friends_online_presence, ads_management, create_event, manage_friendlists, manage_notifications, publish_actions, publish_stream, rsvp_event, user_about_me, user_activities, user_birthday, user_checkins, user_education_history, user_events, user_groups, user_hometown, user_interests, user_likes, user_location, user_notes, user_photos, user_questions, user_relationships, user_relationship_details, user_religion_politics, user_status, user_subscriptions, user_videos, user_website, user_work_history'

  #provider2
  config.omniauth :twitter, ENV['TWITTER_KEY'], ENV['TWITTER_SECRET'],
    :scope => {
     :secure_image_url => 'true',
     :image_size => 'original',
     :authorize_params => {
       :force_login => 'true'
     }
   }

  #provider3
  config.omniauth :google_oauth2, ENV["GOOGLE_KEY"], ENV["GOOGLE_SECRET"]
MZaragoza
  • 10,108
  • 9
  • 71
  • 116
  • 1
    This didn't work for me to force Twitter to make the user re-login. It is using the same account from when I logged in before at some time in the past on my browser. I'm trying to make it so the user can link multiple accounts to their site account. – Dex Jan 09 '15 at 08:07
  • You can create a auth controller that belongs_to a user and a user has_many auths – MZaragoza Jan 09 '15 at 13:26
  • I found the answer. Take out the `:scope =>` and it works just fine using the `omniauth-twitter` gem. `scope` is for Facebook and maybe some other services. – Dex Jan 09 '15 at 13:27
  • I am glad you found the answer . – MZaragoza Jan 09 '15 at 13:28
3

You should give this gem a try. https://github.com/AlexanderZaytsev/domp Quick and Easy to integrate. I had a working setup in about 30m.

dTron
  • 146
  • 5
  • That gem didnt work out for me.Can you share you example please. – fenec Nov 25 '14 at 03:36
  • I am using this gem. There is a little issue though. When I goto (localhost:3000/users/auth/facebook) it give me no route match error. But with rake command I can see that it is defined there – Zain Zafar Dec 21 '14 at 20:49