My Rails 3 app is using Devise and I noticed that my users table was missing it's authentication_token column. Not sure how it's been authenticating users up until now, but in either case I'm looking to add the authentication_token column to repeat what was done in this tutorial: http://ariejan.net/2011/03/27/rails-3-devise-uploadify-no-flash-session-hacks. How do I go about adding this in?
Edit
I did notice that in my config/initializers/devise.rb I had this, which I take is an alternative to auth tokens?
# If true, uses the password salt as remember token. This should be turned
# to false if you are not using database authenticatable.
config.use_salt_as_remember_token = true
Edit 2
I also tried adding the following to my Users model but nothing changed.
devise :database_authenticatable
Here is my current user model:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
Token authentication is only used for API style authentication where you receive a JSON response from another application. By default Devise uses email and password authentication, which is what the :database_authenticatable is configuring.
In order to set your app up this way you can follow either tutorial from the Devise Wiki. Either one of them will point you in the right direction.
Related
I am trying to create a rails 4 app with omniauth devise :
signin with facebook
signin with google
signin with linkedin
signin with twitter
Here, I am able to login in with either facebook, linkedin, twitter or google account. But my problem is: my google account email and linkedin email address are same. And login with google and then login with linkedin is giving me this error:
"Validation failed: Email has already been taken"
This is a problem because devise uses :unique => true in migration file for email field.
Can anyone provide me nice idea to handle this error please?
I think that basically, if handling all the cases properly, this can be really complex unless you choose option 1 below (and even then, there are issues to consider which I outline afterwards). Sorry for the length of this answer!
I'm assuming you've done something like this:
https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
so far, which gets you some of the way but doesn't handle the problem you're encountering.
The way I have done this, I have a User who has_many Identities. Identity stores the name of the external service, the user id it tells you and whatever else you want. This means that the same user can log in with multiple identities (twitter, Facebook…). Have you seen:
http://railscasts.com/episodes/235-omniauth-part-1?view=asciicast
http://railscasts.com/episodes/236-omniauth-part-2?view=asciicast
which help with getting going with the User has_many Identities, but still don't deal with your case.
Then to solve your issue, one option is to detect the validation error you're encountering:
if #user.errors.added?(:email, :taken)
# do whatever you want - e.g. one of the 4 options below.
end
and if it occurs, you could either:
just add the identity to identities associated with that existing user who has the same email address and then sign them in.
or
before adding the identity to the existing user, ask for the password for the existing user account (if the account was originally registered via devise on your system), so you'll need to go to some new controller/action and view that handles this flow.
or
Perhaps send an email confirmation (not devise's standard confirmation) to acknowledge that they are linking their new identity to an existing account. This sounds a bit complicated, because you'll have to store the identity temporarily somewhere (probably in a database if you want to cope with them ending their current session before clicking a confirmation link), flagging it as unconfirmed, until they click a confirmation link in an email (which you'll also have to deal with generating).
or
Perhaps force them to authenticate with the other identity that has the same email address. This has the advantage over the previous option that you can just save the new identity info in the session and get them to authenticate using the other service immediately, but obviously there will be some work to handle the flow there.
Option 1 can be less secure because you are trusting that external services have confirmed the user's email address - which perhaps they have - but you want to be sure otherwise someone could sign up with linkedin using their email address and then sign in to your site and then an attacker could sign up with another service but using the same email address. They could then access the person's account on your site if you didn't confirm they really owned the email address somehow (e.g. by using option 2, 3 or 4). If the external services can confirm they've verified the email addresses, then this option should be ok and is by far the simplest - for example, Facebook have a field that tells you the account has been verified (but see my comments below about services that don't need email addresses). If they're merging with an account registered with you directly (doesn't sound like your situation), you should have confirmed the email address they registered using devise's standard Confirmable feature.
Option 2 sounds like it doesn't apply in your case, because you don't mention that a user can register with you directly via devise; only sign in using external services. This means that they have no password on your site that they know. (You've probably added a dummy one to get the devise validation to pass, unless you've disabled that, but they won't know the password unless you've told them somehow, and it could be confusing to them to do that).
Option 3 sounds do-able, though I haven't tried it. It's a bit more laborious for the user.
Option 4 also sounds do-able, though I haven't tried it either.
So far, I've done option 2 because users can only register with my site either directly using devise or via 1 external service. I'll be adding other services soon, so I plan to use option 4 (perhaps only if external service says they haven't confirmed the email address, and option 1 otherwise).
Options 2, 3 and 4 are a fair bit more work than option 1, so it depends if you can confirm that the external services have verified the email addresses and if not, how paranoid you are about attackers being able to access user accounts on your site. Personally, I err on the side of paranoia!
This might also give you some more useful info:
https://github.com/intridea/omniauth/wiki/Managing-Multiple-Providers
but because omniauth itself doesn't concern itself with model issues, it mostly sidesteps it, though it says for your case that it is "probably sufficiently prudent to assume that they are, in fact, the same person who also created the previous user" but you have to be able to trust the external services as I mentioned above.
There are also other things to consider, such as the case where someone has the same email address registered with Facebook and linked in and has signed in with both on your site (so single user account once you've dealt with your issue) and then changes the email associated with their Facebook account but not linkedin. If you always overwrite the email stored in the user table with the one from the external service, then it'll keep changing if they log in with linkedin and then Facebook (but maybe this doesn't matter to you). Alternatively, they may have different email addresses registered with Facebook and linked in and have logged in with both on your site (so 2 different users on your site) and then they change their linked in email address to be the same as the Facebook one. If you update the email address for a user every time they log in via an external service, you'll have your "Email already taken" error, but in this case you have 2 existing users to merge which could be interesting depending on what else in your database is associated with a user...
Also, I don't think twitter returns an email address, so if the same person has logged in with twitter and linkedin, you won't detect this. Furthermore, I think email is optional with Facebook (you can use a mobile phone number), so the same thing can happen with Facebook. My ideal solution would allow the user to merge arbitrary accounts, obviously having to enter whatever credentials are required to confirm they own the accounts they are merging! I haven't done this yet, but it's on my wish list!
I followed these steps and working fine for me:
1. Gemfile
gem 'omniauth-facebook', '1.4.0'
gem 'omniauth-twitter'
gem 'omniauth-google-oauth2'
2. config/route.rb
devise_for :users, controllers: { omniauth_callbacks: "omniauth_callbacks" }
3. Links
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>
<%= link_to "Sign in with twitter", user_omniauth_authorize_path(:twitter) %>
<%= link_to "Sign in with google", user_omniauth_authorize_path(:google_oauth2) %>
4. controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_filter :authenticate_user!
def all
user = User.from_omniauth(request.env["omniauth.auth"], current_user)
if user.persisted?
flash[:notice] = "you are successfully logged in!!"
sign_in_and_redirect(user)
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
def failure
super
end
alias_method :facebook, :all
alias_method :twitter, :all
alias_method :google_oauth2, :all
end
5. add required fields and model
rails g migration add_social_network_info_columns_to_users name image_url locations
# generate new model Authorization
rails g model Authorization user_id:integer provider uid token secret username
6. models/User.rb
class User < ActiveRecord::Base
require 'securerandom'
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable
has_many :authorizations
# omniauth facebook provider
def self.from_omniauth(auth, current_user)
# check for existing authorization
# Find or create Authorization with: provider, uid, token and secret
authorization = Authorization.where(
:provider => auth.provider,
:uid => auth.uid.to_s,
:token => auth.credentials.token,
:secret => auth.credentials.secret
).first_or_initialize
if authorization.user.blank?
user = current_user.nil? ? User.where('email = ?', auth["info"]["email"]).first : current_user
# save user related data in user table
if user.blank?
User.new(
:email => auth.info.email,
:password => Devise.friendly_token[0,10],
:name => auth.info.name,
:locations => auth.info.location,
:image_url => auth.info.image
)
# since twitter don't provide email,
# so you need to skip validation for twitter.
auth.provider == "twitter" ? user.save!(:validate => false) : user.save!
end
# store authorization related data in authorization table
authorization.username = auth.info.nickname
authorization.user_id = user.id
authorization.save!
end
authorization.user
end
end
6. model/Authorization.rb
class Authorization < ActiveRecord::Base
belongs_to :user
end
source:
https://github.com/mohitjain/social-login-in-rails
I'm weighing up whether to roll my own authentication system (a la the excellent Railscast) or using Devise.
My question is, what are the potential pitfalls when not using Devise but going for something as per the Railscast? Are their security issues I need to think about that are not already covered in the cast? Any other pitfalls you can think of?
Edit: Why am I thinking about not using Devise? Part of the reason I am shying away from Devise is because I am not keen on it's failed log-ins protection. The way Devise does it means anyone can lock anyone else's account if they just know their email address. And it seems to me that on balance, I would be better off rolling my own than getting to know Devise inside out to make these changes, especially if I am going to need to do other things my own way too at some point in the future (which seems likely).
For basic authentication (which means just having a username and a password), rolling out your own wouldn't have any serious pitfalls.
Now, if you also wanted:
Cookies for remembering a logged in user
Recover a forgotten password by sending an email with reset instructions
Require an email confirmation for signing up
Timeout user sessions without activity in a certain period of time
Now these would be quite a bit harder to implement.
So, if you just want a basic authentication system, you can happily go with your own. But if you're worried about the future of your app, then maybe you should just go with Devise. It's not that hard to understand, it provides a ton of features and later you won't have to migrate your data when you actually decide to use Devise.
Edit: So, reiterating what I said. If this is a pet project and you just want to have a basic authentication system and authorization system, where you will only allow certain users to view certain pages, then you're free to implement your own and learn as you go along.
However, if this is something more serious, then I don't see any reason why you shouldn't go with Devise. It reminds me of people creating their own hashing and encryption schemes when they could (and should!) just use something powerful and safe like bcrypt.
I was asking the same question a while back. If you're looking to really dive in and spend some time on authentication, make your own. But if you want to get something pretty standard up fast so you can focus on the features of your app, I would recommend devise.
It doesn't appear the Lockable module is even on by default, but it's easily done either way.
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
...
end
Also if you did use the Lockable module, since locking is based on a number of failed authentication attempts, you can change the maximum number of attempts before triggering a lock in config/initializers/devise.rb
Devise.setup do |config|
...
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
config.maximum_attempts = 20
...
end
Just take a quick read through https://github.com/plataformatec/devise#devise
I've set up ActiveAdmin early in my project and used the default admin_users model for authentication. I've since used Devise to set up a separate User model and have realized it would probably be much smarter to merge the two tables, such that an Administrator can have administrative actions both in Activeadmin and in the front end of the site. How can I configure ActiveAdmin to use the Users model with maybe a column to flag an administrator (eg. is_admin or event a permissions level to make Administrators and Moderators)?
Rails 3.1
ActiveAdmin 0.3.3
Devise 1.4.9
For a quick code block of how to do this using an existing "User" model with activeadmin, the answer is actually really easy. In the ApplicationController:
class ApplicationController < ActionController::Base
def authenticate_admin_user! #use predefined method name
redirect_to '/' and return if user_signed_in? && !current_user.is_admin?
authenticate_user!
end
def current_admin_user #use predefined method name
return nil if user_signed_in? && !current_user.is_admin?
current_user
end
end
And just use what Devise already has set up for authentication. The redirect_to is where you want to send users who ARE signed in and DO NOT have administrative privileges.
ActiveAdmin let's you define your own authentication methods. You can migrate your user tables to have an additional admin column and mark the existing admins as such in it, then set your authentication methods (as specified) in config/initializers/active_admin.rb.
I recently got Devise working. New users sign in, sign up, logout etc etc just fine. Old users however have an issue. I have gotten it to a point where I get a 401 unauthorized, which seems to me that the hash is just incorrectly being created when signing in and of course not matching correctly.
My user model:
class User < ActiveRecord::Base
require "digest/sha1"
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :encryptable, :encryptor => :old_cakephp_auth
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :events
end
Cakephp uses sha1, but I don't know the specifics of how it does things. This obviously doesn't work, which is why I am here:
require "digest/sha1"
module Devise
module Encryptors
class OldCakephpAuth < Base
def self.digest(password, stretches, salt, pepper)
Digest::SHA1.hexdigest("#{salt}#{password}")
end
end
end
end
I got that from the how to add a custom encryptor example. They had this:
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
That didn't work either. Anyone have any ideas?
I saw a variation of this on the create your own custom encryptor wiki. I don't know how I didn't see it before. Perhaps someone updated it recently.
Place the following in your user model. It should overwrite valid password from devise:
def valid_password?(password)
return false if encrypted_password.blank?
Devise.secure_compare(Digest::SHA1.hexdigest(self.password_salt+password), self.encrypted_password)
end
You need to make sure to fill in the password salt you used in cake into all legacy user's rows. You also need to change password to encrypted password according to devise's instructions.
I feel like I may need to add a way encrypt from user model as well for new users. Or perhaps the custom encryptor I created handles that aspect.
I have a little app with Rails and Devise, and until now we were registering new users by Rails console. Now I have been asked to give a view for admin only, where they can signup, delete and view other Users.
My question is, which is the best way to go from here with Devise to accomplish this? I have checked similar questions here, on Devise wiki and other sites, and the conclusion I take from them is to have my own User controller.
What I need basically is a index view to list all users with a link on each one of them for editing and destroy and a new view for signup users. How much code from devise controllers I will new to override?
Also, my user model has the devise module: :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :timeoutable.
Thank you in advance.
From what it sounds like ... you just want have a client side for devise, (the user needs to register, login, etc. because they cant use the console). What I think your asking for, i think you should follow this tutorial to set up devise https://github.com/fortuity/rails3-subdomain-devise/wiki/Tutorial-%28Walkthrough%29 , (i followed this up to Set Up Subdomains, because that is what was important for me in my rails app). It should set up the functionality you are talking about. After you do this, if you find sign out throws an error, change
devise_for :users
in your routes, to
devise_for :users do
get "/users/sign_out" => "devise/sessions#destroy", :as => :destroy_user_session
end
If this wasn't what you were looking for, go into a little more detail