9

Is there any way to configure Devise Omniauth for multiple models without STI?

We have the models Students and Professors and we didn't want to use STI but now we realized that Devise with Omniauth doesn't play well with multiple models.

.rvm/gems/ruby-1.9.3-p125/gems/devise-2.1.0/lib/devise/rails/routes.rb:384:in `devise_omniauth_callback': Wrong OmniAuth configuration. If you are getting this exception, it means that either: (RuntimeError)

1) You are manually setting OmniAuth.config.path_prefix and it doesn't match the Devise one
2) You are setting :omniauthable in more than one model
3) You changed your Devise routes/OmniAuth setting and haven't restarted your server
marc_ferna
  • 5,837
  • 1
  • 18
  • 22
  • 1
    meet same issue here. I'm currently consider connect facebook authentication by hand. =/ – Ray Shih Aug 18 '12 at 06:43
  • We went with a parent class User that does the authentication with Devise Omniauth and then the other models extending from User. Didn't find anything else... – marc_ferna Aug 18 '12 at 19:38
  • @marc_ferna It wont work directly unless you add some logical changes to your code. – Sandip Ransing Aug 20 '12 at 06:56

4 Answers4

3

Hi i came here with a similar problem and i came across with this solution, maybe i will help the next one. In my case i have two devise models where i work with omniauth in different ways, my first model was an user who can sign in with normal devise sign up or with omniauth, the second one was an artist, who just can sign up with regular sing up form, but i still going to need omniauth for authenticate a value from twitter, the verified field(That little check on twitters profiles that means if a famous user is really that guy).

So when i came with i can't have two devise omniauthable models i just decide to use the same omniauth for both.

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def all
    if artist_signed_in? and session["devise.authorize"] == current_artist.email
      session["devise.authorize"] = nil
      if current_artist.artist_profile.update_from_omniauth(request.env["omniauth.auth"])
        if current_artist.artist_profile.verified
          flash[:notice] = "You were verified"
        else
          flash[:alert] = "Twitter go well but you aren't verified there"
        end
        redirect_to edit_artist_profile_path(current_artist.artist_profile)
      else
        flash[:error] = "We can't connect with #{request.env["omniauth.auth"].provider.titleize.split(" ").first}"
        redirect_to edit_artist_profile_path(current_artist.artist_profile)
      end
    elsif !user_signed_in?
      user = User.from_omniauth(request.env["omniauth.auth"])
      if user.persisted?
        flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => user.provider.titleize.split(" ").first
        user.user_profile.from_omniauth(request.env["omniauth.auth"])
        sign_in_and_redirect user, :event => :authentication
      else
        session["count_errors"] = 0 if session["devise.user_attributes"] == nil
        session["devise.user_attributes"] = user.attributes
        redirect_to new_user_registration_url
      end
    else
      flash[:notice] = "You are already log in #{current_user.email}"
      redirect_to root_path
    end
  end

  def after_omniauth_failure_path_for(scope)
    if artist_signed_in? and session["devise.authorize"] == current_artist.email
      session["devise.authorize"] = nil
      edit_artist_profile_path(current_artist.artist_profile)
    else
      super
    end
  end

  alias_method :twitter, :all
end

For the first case, the normal users, we have

elsif !user_signed_in?

it will do the normal process and everything is just like every guide, but for the second case(the verified field and the artist profiles) i send a little session with some random value

session["devise.authorize"]

and i call the link with a new route from my artist profile controller

<%= link_to "Verify with Twitter", artist_profiles_integrate_twitter_path %>

who loads the session and redirects to the user omniauth route

class ArtistProfilesController < ApplicationController
   ...
   def integrate_twitter
      session["devise.authorize"] = current_artist.email
      redirect_to user_omniauth_authorize_path(:twitter)
   end
 end

Then i defined a couple of methods in each classes for working with omniauth, the first creates the user(based on railscast episode "devise-omniauth-revised") and the second just update the field on my artist profile model, you should override after_omniauth_failure_path_for(scope), this just returns the path for failures on login, using the same technique you change the after error path(when fails to connect with twitter, for example, it will redirect to user sign up path, and the session will be around for a while) we can have normal behavior and clean the session in all cases with this.

Hope it helps, regards!

Alexis
  • 4,836
  • 2
  • 21
  • 27
3

Currently, Devise's Omniauthable module does not work with multiple models. (https://github.com/plataformatec/devise/wiki/OmniAuth-with-multiple-models) In a nutshell you need to stop relying on devise to do its magic via "omniauthable" and do this by hand. It is not complicated and once you get first model to work over middelware then it's obvious how to extent for multiple models as it boils down to exactly same code with just different model name.

Steps needed:

  1. Remove omniauthable and devise omniauth setup from devise.rb and model used
  2. Include oauth as middelware(so you catch request before it hits Rails)
  3. Write manual routes - easy peasy
  4. Handle response - amlost the same code you have right now
  5. Handle failure scenario (rejected on oauth provider) - one more block in middelware setup

I solved similar problem and I have detailed explanation here: https://blog.kodius.io/2016/12/20/devise-omniauth-multiple-models/ Code for middleware oauth setup is here: https://github.com/kodius/oauth-example-multiple-models

drKreso
  • 1,030
  • 10
  • 16
2

Currently, Devise's Omniauthable module does not work with multiple models. No need to worry though, as the Omniauthable module is but a simple wrapper around OmniAuth.

https://github.com/plataformatec/devise/wiki/OmniAuth-with-multiple-models

Leonardo
  • 51
  • 4
1

I think rather than writing :omniauthable to individual Student & Professor model. You should generate third model like Omniuser and add :omniauthable settings to it.

class Student < ActiveRecord::Base
  has_one :omniuser
end

class Professor < ActiveRecord::Base
  has_one :omniuser
end
Sandip Ransing
  • 7,583
  • 4
  • 37
  • 48
  • This is the approach that we ended up using but is a Single Table Inheritance and is what we wanted to avoid – marc_ferna Aug 22 '12 at 05:03
  • @marc_ferna actually i am not suggesting STI approach instead suggesting another `omniuser` model that will hold `ominiauth` stuff. – Sandip Ransing Aug 22 '12 at 05:15
  • thanks for your answer. I might have misunderstood what STI is or your solution but based on [this article](http://martinfowler.com/eaaCatalog/singleTableInheritance.html), this solution would involve the model `omniauth` that would have the attributes `email`, `password`, etc, and the model `student` that will have its own attributes. Those two models (and their attributes), would be one single table in our database (Same thing for `professor`). Is that right? If I misunderstood your answer, please explain further ;) – marc_ferna Aug 22 '12 at 05:37
  • @marc_ferna - could you elaborate on how you created the devise vs omniauth models for STI ? I have the same issue where we have three separate user models and two of them need omniauth-facebook. Really puzzled about the model and routes setup – Sandeep Jan 06 '14 at 11:31