#236 OmniAuth Part 2
Oct 13, 2010 | 15 minutes | Plugins, Authentication
In this episode we continue our look at integrating OmniAuth with devise. Here I show how to set up new users with validations.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
Just watched Pt. 1 and was hoping you'd release Pt. 2 soon... I didn't expect it to be THIS soon (one refresh away). Thanks so much, you rock!
Champion. Perfect timing for Rails Rumble.
Oh, 2 railscasts in 1 week! Feels like christmas!
Thanks for posting this so quickly. I got to the end of part 1 and really wanted to finish.
Thanks Ryan for this quick update. I just wanted to point out that when signing in with openid the hash omniauth['user_info'] could be empty if the openid account has not been edited. In that case the user will be redirected to the registration form where she can add the email manually, so nothing bad happens, but I guess coders should be aware of this eventuality.
Thanks Ryan, it was more than I expected! Great job!
Wow. This really added some professionalism to my soon-to-be-released product.
One question. When I register using Twitter or LinkedIn, etc and I go to my profile, how can I remove the requirement to enter my current password to change anything? I mean, signing up with this service actually leaves your password blank???
Thanks!
It's a day late!
Thanks Ryan.
Once again hats off for releasing these two episodes so quickly in succession. Respect.
The thing that eludes me with these authentication methods is how do you tie a user down to a single account? You get a unique uid from the provider but which handle do you have on a newly created user to hone them down into your own app's user model. Is it the email address you make required afterwards?
In other words. How do users authenticate through the devise functionality when they have created a new account solely through omniauth?
Or am I being totally oblivious here.
Cheers, Thanks,
JW
Thanks for screencast.
So devise and omniauth are not good friends really. You should be above average rails programmer to accomplish this. It should be easier somehow.
p.s. i bet devise most difficult authentication solution for now.
Let's say I want to have several sections to a site with different authentications like users. pro_users, admins.
Is there an easy way to re-use code instead of basically repeating these steps 3 times?
The main factor seems to be obtaining the proper scope so that you know to test for current_user, current_pro_user or current_admin.
I can't seem to find an easy method from the controller to address this. I got it to work with brute force respond_to?, but that just seems clunky.
Ryan, have you noticed any issues with the cookie on the system after you authenticate with Facebook? I am getting:
NoMethodError (undefined method `find' for Symbol:Class):
app/controllers/welcome_controller.rb:3:in `index'
After I redirect to my welcome controller. If I start up Safari (using Chrome) everything seems fine.
Hi, can someone please tell me why I would get this error:
undefined local variable or method `new_user_registration_url'
Thanks for the awesome video Ryan.
I was wondering what your take on this is?
http://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
I'm wondering what the best method would be for me to implement.
Ryan - can you post a Rails project pre-configured with Devise and Omni Auth already setup? It would make a great project template to begin a new app.
It would be cool to do a railscast on spam filtering in comments. Unfortunately you seem to get a lot of them like any popular site.
Anyone reading railscast should help Ryan out and mark generic comments pointing to junk websites as spam. It's the least we can do for Ryan.
Thanks Ryan.
PS. I second the comment about creating a skeleton base Rails project with Devise and OmniAuth. Maybe niftySkeleton? :)
Thank you for a great post! I'm wondering how can I expand Omniauth with another providers. Is there any tutorial for that?
Hi,
Thanks for he screencast!
Just wondering how it's possible to update user's datas like email.
The devise method fails: salt error
Thoughts?
sorry, everything's here:
http://github.com/plataformatec/devise/wiki/_pages
Hi guys,
I have a problem: if I register using facebook for instance, I am redirected to the page to choose a username and email. It works fine. But then, if I try to login via the standard method (email + password), I get the following error: "BCrypt::Errors::InvalidHash". Probably because the email is found, but their is no password associated with it (I dont get the error with a bad password for a user registered without omniauth).
What can I do about that?
Thanks for your help.
@apneadiving I've found the devise branch to not be quite as flexible because it only allows a single model. It seems to work behind the scenes much like this railscast too.
I keep seeing @user floating around, where does that get defined? I don't see it explicitly.
I'm having a tough time trying to cleanly integrate two separate models. Right now I'm trying to use two separate models but have them share the authentications table.
Thanks for this - it's a fantastic resource. I think I may have found a potential bug:
1) Sign up with a new account /auth/twitter
2) Once called back to your site, it will ask you to enter an email address
3) Go to /authentications and delete all authentication records
4) You will no longer be able to login (even by going to /auth/twitter again)
When it asks for your email address, it will say it's already in use.
Does anyone have a potential fix for this?
Thanks
Can you use Omniauth to authorize for two different Twitter accounts so that you can tweet to both in your app at the same time?
First let me say I don't know how I would survive without your Railscasts. Moving from infrastructure to development and it has shortened my learning curve.
Question - I've noticed that after a new user authenticates and a record is created if a user deletes all registered services from the authentications page, logout and deletes their cache/cookies you are left with orphaned records. If that user tries to login once more with same credentials the email will be already registered in the database.
Am I correct on this or should I grab some popcorn sit down and kick of the cast again?
Thanks
Ha...looks like Gareth covered my question...my only thought was to not send them to authentications page...but that would suck if user needed to change providers for one reason or another...wouldn't want them to loose existing credentials. Guess you could make the pop up msg warn user that account will be deleted.
Em0ral & Gareth: it's actually easier than that. If you signup using email & password and then logout, you won't be able to signup using facebook or twitter because the email will be already stored in the database.
On possible fix could be searching for the email using the omniauth data from the service (google apps, facebook, etc) and, if it's in the DB, load the user, create a new authentication for him and then do a sign_in_and_redirect.
Sadly this doesn't work for twitter since you can't get the email address from the API.
I guess the quest for me is why would they ever need access to the /authentications page
(sorry for my english)
Very nice.. thanks for coding xD..
But i have a problem
I'm implement this with mongodb using mongoid .. works fine except for when the signup process is successfull the call for find_by_provider_and_uid in uthentications_controller.rb:8:in `create' raise a error: undefined method..
Some idea to fix?
msdark, does mongoid provide that method?
http://mongoid.org/docs/querying/
Here is a more tighter integration to Facebook, by extending Ryans screencast:
http://blog.assimov.net/post/1635826492/facebook-integration-with-omniauth-and-devise-on-rails
Hi,
And tried to implement the code, everything worked well in development environment. when I executed using rails s -e production, I get the error below
omniauth_callbacks_controller.rb:1: uninitialized constant Devise::OmniauthCallbacksController (NameError)
Can anyone help?
Hi,
I see following strange error in my application
http://pastie.org/1351966
How to fix it?
Has anyone successfully adapted this approach to Authlogic? Specifically the parts that allow the user to complete user fields after authenticating? I've checked out madhums excellent demo at https://github.com/madhums/omniauth-authlogic-demo but it doesn't cover that use case.
Hey Ryan thanks for putting this together for us. Its AWESOME and has really accelerated the learning curve for my journeys on rails.
I am having a problem with this though, for some reason I am throwing this error:
"undefined method `id' for user:Symbol"
on the
"sign_in_and_redirect(:user, authentication.user)" step of your instructions.
Here is my code: http://pastie.org/1372000
Have you seen this type of an error before?
I resolved my own issue, turns out that I had somehow created a 'twitter' authentication that was useless at some point. Thus then the sign_in_and_redirect function was called, it was trying to append a non existing user!
DUH! It only took me a few hours...
This is great though thanks!
I just open a issue, about this problem when register with twitter, and then try to login with email and password.
BCrypt::Errors::InvalidHash in Devise/sessionsController#create
invalid hash
If someone have a idea ?
https://github.com/ryanb/railscasts-episodes/issues#issue/3
Thanks,
Thanks so much for the fantastic tutorials. Probably a dumb noob question, but how do you update the sections you want to protect when you add Omniauth to Devise? In the Devise app, the password protection was done by placing the "before filter" in the controllers for the sections to be protected. I didn't see an analogous line in the project or task sections in the source code for the new version with Omniauth. In my app, my links are properly going to the new login with Twitter Omniauth page, but the default password request is still going to the Devise password page, not the new /auth page. How does one update this?
Great stuff Ryan! Nice to have so many options with Authentication
Hi, I am a newbie to ROR. I have read the episodes and done as it says. But I'm stuck at the logout section. My application need facebook authentication only. Users can login using their facebook parameters. I only need the name and uid of the logged user. But when the second user try to login facebook not prompting the login page and redirect to my application with last login user details. could anyone help me in this. Please help me as early as possible. Thanks in advance
I tried to retrieve user email which signed in with Facebook, but it wasn't available throw omniauth['user_info']['email'], but on omniauth['extra']['user_hash']['email']
Is this normal ?
Awesome job Ryan, as per usual. You've been my #1 rails instructor since day one.
I think I found a minor issue. I don't think that your override of create actually works, because 'create' in the Devise RegistrationController calls 'sign_in_and_redirect', and presumably breaks out of the method. So the omniauth session cookie never gets cleared. This wouldn't really matter, except that I need to kick off a couple of background jobs at this moment (between sign up and confirmation). So I'd love to know how to go about overriding the RegistrationController.
Thanks again!
For what it's worth, I solved my problem by adding an after_create method to my user model.
@John , I had the same problem as you did, NoMethodError (undefined method `find' for Symbol:Class):
I figured out the problem, eventually.
You need to change the authentication from find_or_create_by_xx to just find_by_xx , as you aren't creating it until the if statement. It's fairly easy to miss this in the railscast, it happens very quickly.
Hi,
i'm expecting problems with following line
(authentications.empty? || !password.blank?) && super
i cannot use super because of error
super: no superclass method `password_required?' for #<User:0xb129a20>
whats wrong?
Following the tutorial, and it's great. I have refactored quite a bit of code and made several adjustments for clarity sake. I am having one issue though, and it is regarding using any OpenID::Store::Filesystem based provider (google_apps, open_id). It seems that you cannot 'attach' them to an existing account, and they always want to create a new account even if you are logged in. Is this a known issue, and is there a work around?
@Alexey Poimtsev
Because you're missing the :validatable module from Devise which adds the password_required? method.
I found that I still wanted my users to be able to update their username and email address. Because using
user.authentications
allows a user to be created without a password devise will complain when prompting to change the info as it requires acurrent_password
.I used this to allow someone that is logged in via an external auth provider to update without entering a password. You could disable the need for the
current_password
completely if desired.First time posting... huge fan - I started with episode 1 and have learned a lot along the way. Thank you Ryan!
views/registrations/edit.html.erb
user.rb
I'd just like to point out, that Rails > 3.0.4 requires you to add an additional method to
authentications_controller.rb
:Another small update from version 1.0 of omniauth you will need to change the key user_info TO info in the user model ie.
And remember to test if key 'email' exist because twitter don't share that information...
I have followed the omniauth devise facebook app as explained by Ryan in episode 235.After the user authorizes we are getting the error at http://localhost:3000/auth/facebook/callback?code=13444...
The following are the facebook settings :
App Domain : localhost
siteurl: http://locahost:3000/
canvas url: http://localhost:3000/auth/facebook/
I have also tried using 127.0.0.1 in the place of locahost for facebook settings, but i am getting a invalied domain error on facebook settings page.
please tell me where am i going wrong?
I had the same issue. Facebook just doesn't seem to work with localhost unlike Twitter. But I did test it against a public ip that I own and it works as expected. If you don't have a pulbic ip you can try going with some kind of dynamic dns solution.
You can also try looking up this Stackoverflow topic, but your settings seem correct. Maybe lvh.me will work though.
I can use facebook omniauth locally. Perhaps your problem lies with your ssl certificate? I had that problem, you can see it's resolution here
Now, I have a question of my own. Using this method described by rbates, if a user successfully authenticates using Twitter which doesn't provide an email, he is taken to the signup page to complete the registration process. Now, if he doesn't complete the registration process, the @user remains in the session (I don't know how) so the next time you try to register, you'll have that authentication method attached to the created user, which could be a completely different user. How do I solve this? Basically, all I need to do is delete the user if I leave the signup page so how do I do that?
Nevermind, I guess I found the answer to my problem. Here's how I tackled it:
You should change that session info to nil not after the creation action but immediately after building the resource.
Let's say my service stores User records. If a customer signs in with Twitter and creates some records, they will be associated with their new user id. But, later if they sign in with another provider on a different browser, there's no browser session to track, and since the other provider doesn't exist in the database, it will create a separate User record. The customer will not be able to access the records they previously created when they logged in with their Twitter account. It's concerning because I know some users will forget what they used to sign in with originally. Is there a workaround for this?
there is a case the logic doesn't handle correctly in my opinion
suppose you create a user
bob@aol.com
now you log out
ok now you sign in with facebook
it will not find a provider , and not find current_user, which then
will create a new User and try to save it, however that new user will have the same email as the provider, so it will consider duplicate and not allow the user to save
ok so all the problems come from if the user authenticates via oauth but there is no one logged in.
suppose you didn't active confirmable, a malicious user could have registered an account with your email, you can't assume if the email is the same it is the same person. basically you need the user to authenticate as a user in devise as well as authenticate via oauth to link them
ryan did get it right where he creates a user with the same email if the user doesn't exist.
the problem is if a user account already exists with the email and its not logged in.
so in that case, user.save will fail, but then do you redirect to registrations or sign in?
i would think redirecting to sign in would be better since you don't want the user to be trying to create an account with the same email as he once did before.
another issue is, now that the oauth info is in the cookie, it is only erased if the an account is created
since no one logged in yet, it still says logged out, so the user may leave the computer physically, and if a stranger decides to create an account, since it says logged out, that person will add your oauth to his email account.
after checkout out other websites like path.com and quora.com, it seems there is only two ways to do it correctly.
either you have users create and account with a password then link the oauth, path and quora do this
or the other way is you have the oauth itself be the current_user and don't have a user model. many content sites and news sites do this.
@GuilhermeSimoes I took a different approach to this same feature.
In devise/registrations/new.html.erb:
link_to "Cancel registration using #{session['devise.omniauth'][:provider].titleize}", cancel_registration_path(resource)
(only if the 'devise.omniauth' session var is set)
I also switched to using 'devise.omniauth' because the devise documents say it cleans anything in the session that starts with 'devise' on successful authentication.
Great cast as always. =) Thanks Ryan.
Hi guys, I have been trying to get everything mentioned in the video but for some reason I can't get my omniauth data stored in a session for when a new user registers. Everything else works, I can add an authentication to a user and sign in with it.
in authentications_controller.rb I have:
def create
...
end
in registrations_controller.rb I have
def build_resource(*args)
super
if session[:omniauth]
@user.apply_omniauth(session[:omniauth])
@user.valid?
session[:omniauth] = nil
end
end
In the terminal log I can see that the authentications for the user are getting built but once the user registers the authentication does not save and he has to add it again. What am I doing wrong? I can't figure it out!
Thanks in advance.
I have the same issue, did you resolve this?
I also have this problem. And i can't seem to find out how this is fixed
My code:
Is the uid returned remains the same when an user authenticates via omniauth (twitter,facebook etc)? Can I use it as an identifier/foreign key in another model where they are able to provide extra details such as their website? If uid is going to be dynamic (for instance every time they login), then is there a way to relate their extra details with their uid?
hi,
I was following both omniauth episodes in order to get my app running with twitter authorization.
Unfortunately I am stuck when i call /auth/twitter .
I get redirect to my page to the create action in the AuthenticationsController
the weird thing ist that authentication.user is nil when in try to call sign_in_and_redirect
My validations model contains the belongs_to :user relation, but apparently it can not find the associated user.
Any help would be appreciated.
Thanks in advance.
Philip
def create
auth = request.env["omniauth.auth"]
authentication = Authentication.find_or_create_by_provider_and_uid(auth['provider'], auth['uid'])
if authentication
flash[:notice] = "Signed in successfully."
puts authentication
puts authentication.user
sign_in_and_redirect(:user, authentication.user)
else
current_user.authentications.find_or_create_by_provider_and_uid(auth['provider'], auth['uid'])
flash[:notice] = "auth successfull"
redirect_to authentications_url
end
end
Hi again,
so my assumption was that the relation from authentication does not exist.
I added the following line in authentications controller
if authentication && authentication.user.present?
which seems to solve the problem...
I wanted to use multiple providers for 1 account, so I can sign in to the same account using Facebook or LinkedIn.
When e-mails from providers match, I add them to the existing user.
I changed:
user = User.new
to:
Without this change, I was redirected to the new signup form while adding a second provider. And because my e-mail was already in use, there was no way to add both Facebook and LinkedIn
I want to implement 236-omniauth-part-2 in my Project But i get error
NoMethodError in AuthenticationsController#create
undefined method `find_by_provider_and_uid' for Authentication:Class
I define it in stackoverflow. http://stackoverflow.com/questions/13561140/i-am-using-rails-cast-omniauth-and-i-get-this-error
I'm getting:
undefined method 'apply_omniauth' for #<User:0x007f84ec10b940>
when calling:
in the registrations controller.
Is there a better way of doing this via the methods in Ryans later episode? http://railscasts.com/episodes/235-devise-and-omniauth-revised
Sorry for double post, but that's obviously:
user = User.new user.apply_omniauth(omniauth)
in the authentications controller, not registrations
If anyone gets an error about Undefined method `[]' for nil:NilClass this is because of this line: self.email = omniauth['user_info']['email'] if email.blank?
Due to some update this part should be changed 'user_info' to 'info', so self.email = omniauth['info']['email'] email.blank? or self.email = omniauth.info.email email.blank?
How can the else case here would not fail, if we dont find and authentication with the provided params, and as there is no logged in user in this case so the current_user is Nil, so it should fail with Nil class exception when trying to call current_user.authentications.create(....)