I'm new in rails and I'm trying to integrate omniauth and the login with google.
Surfing on the web i found a way to do it, but i got this error:
undefined method `[]' for nil:NilClass
app/models/user.rb:22:in `find_for_open_id'
app/controllers/users/omniauth_callbacks_controller.rb:17:in `open_id'
I think that is because i don't access correctly the access token information.
here is the code:
Model users.rb
def self.find_for_open_id(access_token, signed_in_resource=nil)
data = access_token['user_info']
if user = User.find_by_email(data["email"])
user
else # Create a user with a stub password.
User.create!(:email => data["email"], :password => Devise.friendly_token[0,20])
end
end
omniauth_callbacks_controller.rb
def open_id
# You need to implement the method below in your model
#user = User.find_for_open_id(env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.open:id_data"] = env["openid.ext1"]
redirect_to new_user_registration_url
end
end
devise.rb
config.omniauth :open_id, :store => OpenID::Store::Filesystem.new("/tmp"), :name => 'open_id', :identifier => 'https://www.google.com/accounts/o8/id'
I don't know what else i can show you.
For me the problem is here:
data = access_token['user_info']
access_token['user_info'] returns null.
Am I doing something wrong? Is there other way to access the 'user_info' and then the e-mail?
Thanks in advance. Hope i'm clear.
You should just be using data = access_token['info'] instead of data = access_token['user_info']
From there you have access to data['email], data['first_name'], and data['last_name'].
Related
I've gotten the omniauth to work with google by following this tutorial. The problem I'm currently having is that instead of creating the user when they sign up, I want to route them to a finish registration page where they have to enter additional data. This is similar to how pastebin handles their oauth registration.
UserModel (taken from tutorial):
def self.find_for_google_oauth2(access_token, signed_in_resource=nil)
data = access_token.info
user = User.where(:provider => access_token.provider, :uid => access_token.uid ).first
if user
return user
else
registered_user = User.where(:email => access_token.info.email).first
if registered_user
return registered_user
else
user = User.create(name: data["name"],
provider:access_token.provider,
email: data["email"],
uid: access_token.uid ,
password: Devise.friendly_token[0,20],
)
end
end
end
omniauthCallBacksController
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
#user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
It looks like the problem is that your find_for_google_oauth2 is creating the user. This means that the else in your google_oauth2 callback isn't getting hit, so it'll never go to your new_user_registration_url. One solution is to separate your find and create steps rather than combining them in find_for_google_oauth2. Then, when the find doesn't find an existing user, you hit your else in your google_oauth2 callback and the omniauth data is put in the session and you get sent to new_user_registration_url. Then once they've entered the extra info and submit the form, you can use that, combined with the omniauth data you saved in the session, to create the user.
Two things about putting the omniauth data in the session:
You probably want to pick what you want to keep from the omniauth hash (especially if you're using CookieStore for the session storage) as it's quite large.
If you're using the CookieStore and Rails < 4 then the omniauth data being stored in the session (i.e. in the cookie on the user's computer) is unencrypted.
I followed the steps that are described in https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview and have a method in user model like this:
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if user = self.find_by_email(data.email)
user
else # Create a user with a stub password.
self.create!(:email => data.email, :password => Devise.friendly_token[0,20])
end
end
I intermittently get errors like
A NoMethodError occurred in omniauth_callbacks#facebook:
undefined method email' for "false":String
app/models/user.rb:138:infind_for_facebook_oauth'
that I haven't been able to reproduce. What is the source of this problem?
I am not sure what causes this either. Here's a work-around that simply creates a new object:
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if data == "false"
self.new
elsif user = self.find_by_email(data.email)
user
else # Create a user with a stub password.
self.create!(:email => data.email, :password => Devise.friendly_token[0,20])
end
end
The controller code shown in the example will then work - it will redirect the user to sign up.
I setup Facebook login with Devise and omniauth with these instructions https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
The Devise wiki gives some instructions for getting facebook info from the hash stored in this variable request.env['omniauth.auth'] See bottom for the hash.
For example, Devise wiki has these two methods for the User.rb model
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if user = User.where(:email => data.email).first
user
else # Create a user with a stub password.
User.create!(:email => data.email, :password => Devise.friendly_token[0,20])
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
user.email = data["email"]
end
end
end
So, using the hash below, I added the following to those two methods to get the name and image
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if user = User.where(:email => data.email).first
user
else # Create a user with a stub password.
User.create!(:email => data.email, :password => Devise.friendly_token[0,20], :name => data.name, :image => access_token.info.image) #I added access_token.info.image based on first answer
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["raw_info"]
user.email = data["email"]
user.name = data["name"]
user.image = access_token.info.image #i changed this based on first answer below
end
end
end
Then in my view, I added the following to show the user name and image
<p>Name:<%= user.name %></p>
<p>Image: <%= image_tag user.image %>
However, only the name is showing. No image.
In my database, I have a name and an image column. The name from Facebook is being stored, but the image column says 'nil'
Any ideas how I can get the image to work?
Hash stored in request.env['omniauth.auth'] https://github.com/mkdynamic/omniauth-facebook/blob/master/lib/omniauth/strategies/facebook.rb#L31-47
info do
prune!({
'nickname' => raw_info['username'],
'email' => raw_info['email'],
'name' => raw_info['name'],
'first_name' => raw_info['first_name'],
'last_name' => raw_info['last_name'],
'image' => "#{options[:secure_image_url] ? 'https' : 'http'}://graph.facebook.com/#{uid}/picture?type=square",
'description' => raw_info['bio'],
'urls' => {
'Facebook' => raw_info['link'],
'Website' => raw_info['website']
},
'location' => (raw_info['location'] || {})['name'],
'verified' => raw_info['verified']
})
end
The image can be found at env["omniauth.auth"]["info"]["image"]. So in your case, access_token.info.image.
If you want to take a good look at the hash of nested hashes returned and see for yourself where everything is, put this as the first line of your callback controller:
render :text => "<pre>" + env["omniauth.auth"].to_yaml and return
EDIT: Ok, so here's what you need to do:
def self.find_for_facebook_oauth(omniauth)
if user = User.find_by_email(omniauth.info.email)
if omniauth.info.image.present?
user.update_attribute(:image, omniauth.info.image)
end
user
else # Create a user with a stub password.
User.create!(:email => omniauth.info.email,
:name => omniauth.info.name,
:image => omniauth.info.image,
:password => Devise.friendly_token[0,20])
end
end
As for the other method, if I'm not mistaken, it should look like this:
def self.new_with_session(params, session)
super.tap do |user|
if omniauth = session["devise.facebook_data"]
user.email = omniauth.info.email
user.name = omniauth.info.name
user.image = omniauth.info.image
end
end
end
But when is this method used? It's used by Devise when something goes wrong when creating your user. Imagine that the authentication provider doesn't give you an email (Twitter, for example, does this), what can you do? Well, you can redirect the user to your sign up page where he can complete the signup process. But if you redirect the user, you lose the data received by the oauth. The solution is to put this data into the session.
So in your controller, you should have something like:
if user.save
sign_in_and_redirect user, :event => :authentication
else
session["devise.facebook_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
Another problem, however, is that most of the times the data returned by the authentication provider is too big to fit in the session, so we have to pick exactly what we want to put in the session. Since you are only getting a name and an image, you can trim the extra info like so:
session["devise.facebook_data"] = env["omniauth.auth"].except('extra')
In a recent project, facebook Users can login using their Facebook UID to upload picture submissions based on file uploads or uploads from their personal albums etc.
Everything works quite nice on my local system in the development environment. Login via Facebook, Logout, Upload - all great.
In production though I'm facing a unknown and hard to debug problem. It seems that every once in a while (actually reproducable when uploading a new Submission to the system) the session is lost, the picture is NOT uploaded and the facebook user is logged out (!).
I'm using devise and omniauth. Omniauth is integrated into Devise.
Following is all the code that touches Devise/Omniauth or the User.
app/models/user.rb
class User < ActiveRecord::Base
devise :omniauthable, :rememberable, :omniauth_providers => [:facebook]
def self.create_with_omniauth(auth)
u = User.find_by_uid(auth["uid"])
return u unless u.nil?
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["user_info"]["name"]
user.email = auth['user_info']['email']
end
end
def after_signin_path
'/competition'
end
end
Database contains all needed fields for :rememberable, I hope.
app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model
#user = User.create_with_omniauth(env["omniauth.auth"])
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
#user.update_attributes!(:current_auth_token => env["omniauth.auth"]['credentials']['token'], :last_language => I18n.locale.to_s, :updated_at => Time.now, :remember_created_at => Time.now)
sign_in_and_redirect(:user, #user)
else
redirect_to '/competition'
end
end
protected
def after_omniauth_failure_path_for resource
'/competition'
end
end
config/initializers/devise.rb
OmniAuth.config.full_host = "http://#{APP_CONFIG[:domain]}"
Devise.setup do |config|
config.mailer_sender = "devise#myapp.host.com"
require 'devise/orm/active_record'
config.stretches = 10
config.encryptor = :bcrypt
config.timeout_in = 3.days
config.pepper = "2a4b8b2ed9e12e553a7a542176f2ace1af62c062f3ba203a590b8b6307f33042b394922807a840004a3dcdf1c4e97ae085fe2c29654ddaeab7c60f431a8078abb"
config.omniauth :facebook, APP_CONFIG[:facebook_app_id], APP_CONFIG[:facebook_app_secret], {
:scope => "email,user_photos,user_photos,publish_stream,offline_access",
:client_options => {
:ssl => {
:ca_file => "/etc/pki/tls/certs/ca-bundle.crt"
}
}
}
end
There are no auth-related methods in application_controller.rb.
routes.rb:
The interesting part below:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
match '/logout_fb' => 'start#logoutfb'
authenticate :user do
get '/users/connect/:network', :to => redirect("/users/auth/%{network}")
end
Somehow I cannot get to understand the authenticate block, which according to another post should be helpful.. ideas on this too?
So many theories:
One is that the facebook function in the omniauth_callbacks_controller runs aside of the users' session, and hence sign_in_and_redirect won't work. So I had the idea of redirecting to another page like '/auth?uid=xxx' but this sounds both wrong, insecure and not stable.
Any help or hints are appreciated!
A bit of a long shot but try turning off protect_from_forgery - I had some issues with sessions disappearing and it turned out to be the issue discussed here https://github.com/intridea/omniauth/issues/203
In my config/initializers/omniauth.rb, I had to add the following:
OmniAuth.config.full_host = "http://yourdomain.com" # Or have an environment specific URL.
You are using devise but you are not using it's own helpers. For instance, you've defined your own current_user method. To be honest, I can't see any obvious mistakes you've made, so it's just a desperate tip.
what kind of a session store do you use locally and what in production?
When you say "facebook user is logged out", this user is still logged in to facebook, but lost his session at yourapp.com ?
Are you sure that user.id is never nil or that you anywhere else than in .destroy set session[:user_id]= some_nil_variable ?
I'm using omniauth and devise to log users in with their facebook acounts, everything works however when I try to pull their email out of the hash I get this error:
NoMethodError in AuthenticationsController#create
undefined method `id' for "/":String
Here's the full log of the error : http://pastie.org/1698569
The error goes away and it lets me log in just fine after I refresh!
EDIT: It turns out that its line 22 in my authentications controller
sign_in_and_redirect_to(:user, root_path)
For some reason after running this method I can't sign_in the :user
def apply_facebook(omniauth)
if (extra = omniauth['extra']['user_hash'] rescue false)
self.email = (extra['email'] rescue '')
end
end
However, if I don't run that method, then it can sign_in_and_redirect_to just fine
Here's my controllers/model http://pastie.org/1698453
Really appreciate any help
You can't use root_path as the second parameter for sign_in_and_redirect . Here are some available ways you can use it:
sign_in_and_redirect :user, #user # sign_in(scope, resource)
sign_in_and_redirect #user # sign_in(resource)
sign_in_and_redirect #user, :event => :authentication # sign_in(resource, options)
sign_in_and_redirect #user, :bypass => true # sign_in(resource, options)
Since your second parameter isn't either a resource or options (it's a string), you're getting an error. You need to change it to:
sign_in_and_redirect(:user, user) # based on your pastie
If you want to customize the return path to force it to go to a different URL after sign-in, you can do something like this in your ApplicationController:
def after_sign_in_path_for(resource)
"/go/to/this/path"
end