I'm trying to build Facebook OAuth into my existing Authlogic login system. I have the OAuth part complete, and stored the facebook access_token. The problem I'm facing is to actually log the user in (create a session) without the user typing in their password.
#facebook's OAuth callback
def callback
access_token = client.web_server.get_access_token(params[:code], :redirect_uri => redirect_uri)
fb_user = JSON.parse(access_token.get('/me'))
#user = User.find_by_facebook_id(fb_user["id"]) || User.find_by_email(fb_user["email"]) || User.new
#user.update_attributes({
:facebook_id => fb_user["id"],
:first_name => fb_user["first_name"],
:last_name => fb_user["last_name"],
:gender => fb_user["gender"],
:email => fb_user["email"],
:timezone => fb_user["timezone"],
:locale => fb_user["locale"],
:facebook_url => fb_user["link"],
:facebook_access_token => access_token.token
}) #unless #user.updated_at < 2.days.ago
# TODO: set current_user
# Maybe something like this?
# #user_session = UserSession.new({
# :remember_me => true,
# :password =>"[FILTERED]",
# :email => email
# }).save
flash[:success] = "Welcome, #{#user.name}"
redirect_to :root
end
Nevermind I figured it out. It was in the README the whole time.
UserSession.new(#user, true) //true = persistent session
Related
I am using Devise + Omniauth to enable Facebook signup in my application. When I was developing it, I encountered no problems. Same with deploying it to my remote server. The problem is, other people keep encountering the same error:
TypeError (no implicit conversion of Symbol into Integer):
app/models/user.rb:67:in `find_for_facebook_oauth'
app/controllers/users/omniauth_callbacks_controller.rb:4:in `facebook'
I have the following code for the User model user.rb:
def self.find_for_facebook_oauth( data, signed_in_resource=nil)
user = User.where(:email => data.info.email).first
unless user
params =
{
:user =>
{
:username => data.uid,
:email => data.info.email,
:password => Devise.friendly_token[0,20],
:user_profile_attributes =>
{
:first_name => data.extra.raw_info.first_name,
:last_name => data.extra.raw_info.last_name,
:remote_image_url => data.extra.raw_info.image,
},
:user_auths_attributes =>
{
:uid => data.uid,
:provider => data.provider
}
}
}
user = User.create!(params[:user])
end
return user
end
Where line 67 is the user = User.create!(params[:user])
And omniauth_callbacks_controller.rb:
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
#user = User.find_for_facebook_oauth(request.env["omniauth.auth"])
if #user.persisted?
sign_in_and_redirect #user, :event => :authentication #this will throw if #user is not activated
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
Where line 4 is #user = User.find_for_facebook_oauth(request.env["omniauth.auth"])
The server logs also show the GET parameters:
Parameters: {"code"=>"[some long string of number and letters]", "state"=>"[another string of numbers and letters]"}
Update:
The logger outputs the following for request.env["omniauth.auth"]:
#<OmniAuth::AuthHash credentials=#<OmniAuth::AuthHash expires=true expires_at=1401992074 token="*"> extra=#<OmniAuth::AuthHash raw_info=#<OmniAuth::AuthHash email="*" first_name="*" gender="male" id="*" last_name="*" link="https://www.facebook.com/*" locale="en_US" name="*" timezone=8 updated_time="2014-04-05T09:29:22+0000" username="*" verified=true>> info=#<OmniAuth::AuthHash::InfoHash email="*" first_name="*" image="http://graph.facebook.com/*/picture?type=square" last_name="*" name="*" nickname="*" urls=#<OmniAuth::AuthHash Facebook="https://www.facebook.com/*"> verified=true> provider="facebook" uid="*">
Update 2:
Logging the params[:user] provides the following values:
Params: {:username=>"*", :email=>"*", :password=>"iePVLt7XEWk4YwPjja6n", :user_profile_attributes=>{:first_name=>"*", :last_name=>"*", :remote_image_url=>"http://graph.facebook.com/*/picture?type=square"}, :user_auths_attributes=>{:uid=>"*", :provider=>"facebook"}}
Update your params hash as below:
params =
{
:user =>
{
:username => data.uid,
:email => data.info.email,
:password => Devise.friendly_token[0,20],
:user_profile_attributes =>
{
:first_name => data.extra.raw_info.first_name,
:last_name => data.extra.raw_info.last_name,
:remote_image_url => data.info.image ## Removed comma and updated the method
},
:user_auths_attributes =>
[{
:uid => data.uid,
:provider => data.provider
}] ## Enclosed within array [] brackets
}
}
Looking at the params hash given by you, I can tell that a User and Profile have a 1-1 Relationship whereas User and Auths has a 1-M Relationship. In that case, user_auths_attributes must be passed as an Array.
TypeError (no implicit conversion of Symbol into Integer)
You were getting the above error because user_auths_attributes was being interpreted as an array and not a hash. So when Ruby saw params[:user][:user_auths_attributes][:uid] it was trying to take the last key and turn it into params[:user][:user_auths_attributes][0] or at least find some integer value it could be converted to index the Array.
I found only this issue:
:remote_image_url => data.extra.raw_info.image # In data I see only data.info.image
replace with
:remote_image_url => data.info.image
But it is not a solution for your question.
Try to debug data from params[:user]. From exception it looks like that you use some Hash on property which is Integer.
I was wondering if there is a way to include checking the current URL during login with devise.
Say that I have a user model with field :url, and that along with :email and :password, you also check if the current url matches with the user's :url field.
I was thinking I should do this in devise's self.find_for_database_authentication method and I currently have this:
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login).downcase
where(conditions).where('$or' => [{:username => /^#{Regexp.escape(login)}$/i}, {:email => /^#{Regexp.escape(login)}$/i}]).first
else
where(conditions).first
end
end
but what should I add to it so that it checks the current URL?
Note: I am using mongoid
Thanks in advance!
You should simply add the url to the query, something like this:
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
login = conditions.delete(:login).downcase
url = conditions.delete(:url ).downcase
if login && url
where(conditions).where(
:url => {"$eq" => url},
'$or' => [
{:username => /^#{Regexp.escape(login)}$/i},
{:email => /^#{Regexp.escape(login)}$/i}
]
).first
else
where(conditions).first
end
end
Where the hash it is sort of an implicit "and"
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'].
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')
I'm trying to implement a simple login system in Rails, but when I try to display the username of a logged in user, I get this error:
can't convert Symbol into Integer
Extracted source (around line #60):
57: </ul>
58: <% if session[:logged_in] %>
59: <% user = session[:user] %>
60: <p class="pull-right">Howdy, <strong><%= user[:username] %></strong>!</p>
61: <% end %>
62: </div>
63: </div>
My model code is here:
require 'digest'
class User < ActiveRecord::Base
before_save {|user| user.password = Digest::SHA1.hexdigest(user.password)}
attr_accessible :username, :password, :email
validates_length_of :username, :password, :minimum => 7
validates_presence_of :username,:password,:email, :on => :create
validates_format_of :email, :with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
end
This is how I set session[:user]:
def create
if User.find(:all, :conditions => {:username => params[:username], :password => Digest::SHA1.hexdigest(params[:username])})
user = User.find(:all, :conditions => {:username => params[:username], :password => Digest::SHA1.hexdigest(params[:password])})
session[:user] = user
session[:logged_in] = true
redirect_to(:root, :notice => "Thanks for logging in!")
else
redirect_to(:new, :notice => "You supplied an invalid username/password combination.")
end
end
Probably session[:user] is not a Hash, as you expect it to be, but an Array. Thus subscripting it with anything other than an integer is not valid.
How to fix this? Change the code that is actually setting the session variable (like session[:user] = XYZ).
EDIT: User.find(:all, ...) returns an array, so as I assumed, you are assigning an array to session[:user]. You should only assign the first user found (and in fact, there should be only one matching the criteria). Even better, you should only store the username in the session and fetch it from the database if needed:
def create
user = User.where(:username => params[:username], :password => Digest::SHA1.hexdigest(params[:username])).first
if user
session[:user_id] = user.id
else
redirect_to(:new, :notice => "You supplied an invalid username/password combination.")
end
end
Then in the action associated with your view:
def ...
#user = User.find(session[:user_id])
unless #user
# redirect to error page, user was deleted in the meantime
end
end
Then in the view:
<%= #user.username %>
Dumping the whole User object into your session is a bad idea, and is probably why you're not getting back what you expect. You should implement something like #to_session on your User class that returns a hash with the minimum required information. Something like:
def to_session
{:id => id, :username => username, :email => email}
end
Then when you set the session:
session[:user] = user.to_session