Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use separate authentication model with Devise on Rails

I have a simple solution I've made myself with the following objects:

  • Account (has token field, that is returned when authenticating and used in API calls)
  • Authentication (has auth_type/auth_id and reference to Account)

I have a separate Authentication model to be able to connect several ways of login (device UUID, email/password, twitter, facebook etc). But it seems that in all examples of Devise you use it on the User (Account) model.

Isn't that less flexible? For example OmniAuth module stores provider and id on the User model, what happens if you want to be able to login from both Twitter and Facebook, there is only room for one provider?

Should I use Devise on my Account model or the Authentication model?

like image 288
thejaz Avatar asked Feb 01 '13 13:02

thejaz


People also ask

How does devise Current_user work?

current_user is a Devise helper that accesses the details of the user who is currently signed in to the application. For instance, if you sign in with [email protected] , the current_user helper would return the user model for [email protected] . So, when using current_user.

What does Devise gem do in Rails?

Devise is the cornerstone gem for Ruby on Rails authentication. With Devise, creating a User that can log in and out of your application is so simple because Devise takes care of all the controllers necessary for user creation ( users_controller ) and for user sessions ( users_sessions_controller ).

What is authentication and authorization in Rails?

Authentication is the process of verifying who you are. Authorization is the process of verifying that you have access to resources.


2 Answers

Have been recently working on a project where I was using Devise to keep user's tokens for different services. A bit different case, but still your question got me thinking for a while.

I'd bind Devise to Account model anyway. Why? Let's see.

Since my email is the only thing that can identify me as a user (and you refer to Account as the User) I would place it in accounts table in pair with the password, so that I'm initially able do use basic email/password authentication. Also I'd keep API tokens in authentications.

As you've mentioned, OmniAuth module needs to store provider and id. If you want your user to be able to be connected with different services at the same time (and for some reason you do) then obviously you need to keep both provider-id pairs somewhere, otherwise one will simply be overwritten each time a single user authenticates. That leads us to the Authentication model which is already suitable for that and has a reference to Account.

So when looking for a provider-id pair you want to check authentications table and not accounts. If one is found, you simply return an account associated with it. If not then you check if account containing such email exists. Create new authentication if the answer is yes, otherwise create one and then create authentication for it.

To be more specific:

#callbacks_controller.rb
controller Callbacks < Devise::OmniauthCallbacksContoller
  def omniauth_callback
    auth = request.env['omniauth.auth']
    authentication =  Authentication.where(provider: auth.prodiver, uid: auth.uid).first
    if authentication
      @account = authentication.account
    else
      @account = Account.where(email: auth.info.email).first
      if @account
        @account.authentication.create(provider: auth.provider, uid: auth.uid,
         token: auth.credentials[:token], secret: auth.credentials[:secret])
      else
        @account = Account.create(email: auth.info.email, password: Devise.friendly_token[0,20])
        @account.authentication.create(provider: auth.provider, uid: auth.uid,
         token: auth.credentials[:token], secret: auth.credentials[:secret])
      end
    end
    sign_in_and_redirect @account, :event => :authentication
  end
end

#authentication.rb
class Authentication < ActiveRecord::Base
  attr_accessible :provider, :uid, :token, :secret, :account_id
  belongs_to :account
end

#account.rb
class Account < ActiveRecord::Base
  devise :database_authenticatable
  attr_accessible :email, :password
  has_many :authentications
end

#routes.rb
devise_for :accounts, controllers: { omniauth_callbacks: 'callbacks' }
devise_scope :accounts do
  get 'auth/:provider/callback' => 'callbacks#omniauth_callback'
end

That should give you what you need while keeping the flexibility you want.

like image 170
JazzJackrabbit Avatar answered Oct 18 '22 19:10

JazzJackrabbit


You may separate all common logic to module and use only same table.

module UserMethods
  #...
end

class User < ActiveRecord::Base
  include UserMethods
  devise ...

end  

class Admin < ActiveRecord::Base
  include UserMethods
  self.table_name = "users"
  devise ...
end

And configure all devise model separately in routes, views(if necessary, see Configuring Views). In this case, you may easily process all different logic.

Also note that if you are in a belief that devise is for user model only, then you are wrong.

For ex. - rails g devise Admin

This will create devise for admin model.

More information here.

like image 1
My God Avatar answered Oct 18 '22 21:10

My God