Rails: getting a token from session or database - ruby-on-rails

I followed along with Ryan Bates' authenticating with Facebook Railscast http://media.railscasts.com/assets/episodes/videos/360-facebook-authentication.ogv in which he uses gem 'omniauth-facebook' to authenticate with Facebook. At the end of the Railscast, he introduces Koala, which allows you to interact with the open graph api. Ryan gives instructions to pass the oath token as a parameter into this
#graph = Koala::Facebook::API.new(your_oauth_token)
I'm having trouble getting this to work because I'm not sure where to get the oath token from. Let me explain....
In the sessions_controller.rb, we have this
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url
end
which saves the omniauth information to the database in the User model
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
so I'm guessing I need the user.oath_token passed into
#graph = Koala::Facebook::API.new(your_oauth_token)
but I can't get it to work.
Can you please imagine that I have a Main controller and an index action.
Main_controller.rb
def index
#graph = Koala::Facebook::API.new(how do I get the oauth token in here?)
end
Question
How would I get the oath token (from either the session or the database) into the index method of the main controller?
For example, using the helper method in application_controller.rb
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
I tried to do this in main_controller.rb
def index
#graph = Koala::Facebook::API.new(current_user.oauth_token)
#graph_data = #graph.get_object("/me/statuses", "fields"=>"message")
end
However, when I tried to loop through data using the following
Views/main/index.html.erb
<% if current_user %>
Looping through your statuses:<br />
<ul>
<% #graph_data.each do |status| %>
<%= status["message"] %> (<i><%=status["updated_time"]%></i>)<hr>
<% end %>
</ul>
<% end %>
It didn't give me any status updates

The error is that current_user is returning nil. This is probably because session[:user_id] is not set (ie user is not logged in). You should have a check in index to protect against this:
def index
if current_user
#graph = Koala::Facebook::API.new(current_user.oauth_token)
else
# User isn't logged in; do something else
end
end

Related

Rails 4: User Authentication - NoMethodError

I've setup a moreless simple social user authentification on top of devise using Google, Linkedin, Dropbox and Github.The Dropbox authentication does not work, instead it gives that error on the callback URL(http://localhost:3000/users/auth/dropbox/callback):
NoMethodError in Users::OmniauthCallbacksController#dropbox
undefined method `first' for nil:NilClass
Issue: User Model (line 8)
My Code:
Callbacks Controller:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
user = User.from_omniauth(env['omniauth.auth'], current_user)
if user.persisted?
sign_in user
flash[:notice] = t('devise.omniauth_callbacks.success', :kind => User::SOCIALS[params[:action].to_sym])
if user.sign_in_count == 1
redirect_to edit_user_registration_path
else
redirect_to root_path
end
else
session['devise.user_attributes'] = user.attributes
redirect_to new_user_registration_url
end
end
User::SOCIALS.each do |k, _|
alias_method k, :all
end
end
User Model:
# omniauth Gem
def self.from_omniauth(auth, current_user)
authorization = Authorization.where(:provider => auth.provider, :uid => auth.uid.to_s,
:token => auth.credentials.token,
:secret => auth.credentials.secret).first_or_initialize
authorization.profile_page = auth.info.urls.first.last unless authorization.persisted?
if authorization.user.blank?
user = current_user.nil? ? User.where('email = ?', auth['info']['email']).first : current_user
if user.blank?
user = User.new
user.skip_confirmation!
user.password = Devise.friendly_token[0, 20]
user.fetch_details(auth)
user.save
end
authorization.user = user
authorization.save
end
authorization.user
end
def fetch_details(auth)
self.email = auth.info.email
self.username = auth.info.name
self.avatar = URI.parse(auth.info.image)
end
I appreciate each help! Thanks in advance.
To answer your question directly:
The undefined method "first" for nil::NilClass is happening because you are attempting to call the method first on an empty, or nil object.
It's probably in your user model where you are attempting to find a User from a current_user.
if authorization.user.blank?
user = current_user.nil? ? User.where('email = ?', auth['info']['email']).first : current_user
#This will cause the error that you are describing if both the current_user is nil and there is no User whose email is auth['info']['email']
Now, There's a few things wrong with this. If they are attempting to log in to your application, then current_user at this stage should be unset.
You could try changing this to
user = User.where(email: auth['info']['email']).first_or_create
Which will create a new instance of User, if one does not exist with the email provided in the Authorization.
Then you can continue with
user.persisted?
which returns true for an existing user, and false for a new instance of User

bypassing methods in model from two different variables in controller?

I have this code in my controller and model
SessionsController
def create
user = User.from_omniauth(env["omniauth.auth"])
user.skip_password_validation = true
unless user.present?
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
# Log the user in and redirect to the user's show page.
else
# Create an error message.
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
else
log_in user
redirect_to user
end
end
user model
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.email = auth.info.email
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
end
As i have two, different, login methods in my sessions controller I need to skip methods in my User model. I have looked in to a few things such as before filters, before actions and attribute accessors and search the web but cant seem to find out how to skip methods in the model so I can bypass those for each user variable I have assigned in the sessions controller? Ie, one for omniauth facebook and the other for just standard login.
If I understood the question properly, in case of plain auth there would be no env["omniauth.auth"] set, so one might go with:
def create
user = if env["omniauth.auth"]
User.from_omniauth(env["omniauth.auth"]).tap do |u|
u.skip_password_validation = true
end
else
User.find_by(email: params[:session][:email].downcase).tap do |u|
unless u && u.authenticate(params[:session][:password])
# Create an error message.
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
return
end
end
end
log_in user
redirect_to user
end

How to save the request.referrer for facebook omniauth in Rails 4

I have two facebook buttons in my application. One on users/sign_up and other on /visitors/owner-faq. I want to save the request.referrer in order to know from which page the user has signed up. I had implemented the same for external signup(which uses a form) and it worked. Now I'm unable to implement the same for facebook omniauth.
Code:
#user.rb
def self.from_omniauth(auth)
email = auth.info.email
user = User.find_by_email(email) # first tries to find an existing user who signed up normal way with the same email to sign them in
if user && user.confirmed?
user.provider = auth.provider
user.uid = auth.uid
return user
end
where(provider: auth.provider, uid: auth.uid).first_or_create do |user| # then tries to find the user who authenticated through FB, and if
user.email = auth.info.email # not present, creates that user
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.social_photo_url = auth.info.image
user.skip_confirmation!
end
end
#users/omniauth_callbacks_controller
def facebook
#user = User.from_omniauth(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"]
Rails.logger.info(#user.errors.inspect)
redirect_to new_user_registration_url
end
end
#application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_filter :store_referrer_url, :only => [:new]
private
def store_referrer_url
session[:referrer] = URI(request.referer).path
end
end
Attempt #1:
I managed to save the request.referrer like this in users/omniauth_callbacks_controller
def facebook
#user = User.from_omniauth(request.env["omniauth.auth"])
#user.referrer_url = session[:referrer] #here
#user.save!
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"]
Rails.logger.info(#user.errors.inspect)
redirect_to new_user_registration_url
end
end
But the problem here is the value of referrer_url is overwritten when the existing user logs in from another page. I don't want to get the referrer_url to be overwritten.
Attempt #2:
I tried to save the request.referrer in the from_omniauth(auth) method User model like this
def self.from_omniauth(auth)
email = auth.info.email
user = User.find_by_email(email) # first tries to find an existing user who signed up normal way with the same email to sign them in
if user && user.confirmed?
user.provider = auth.provider
user.uid = auth.uid
return user
end
where(provider: auth.provider, uid: auth.uid).first_or_create do |user| # then tries to find the user who authenticated through FB, and if
user.email = auth.info.email # not present, creates that user
user.password = Devise.friendly_token[0,20]
user.first_name = auth.info.first_name
user.last_name = auth.info.last_name
user.social_photo_url = auth.info.image
user.referrer_url = session[:referrer]
user.skip_confirmation!
end
end
But it gave me this error
undefined local variable or method `session' for #<Class:0x0000000e1c46f8>
Any suggestions would be greatly helpful.
If you want the referrer_url property to stay the same after it's first set, you should use the OR Equal operator, that will only set the value if it's currently nil or false:
#user.referrer_url ||= session[:referrer]
If I understand correctly, your facebook callback is called both on sign up and login, and of course the value would be overwritten. OR Equals will prevent the value from being overwritten once it's set.
Regarding Attempt #2, the session hash is only available in the controller and the view, so you can't use it in the model.
Another possibility:
You are setting session[:referrer] in your store_referrer_url method which is called in a before_filter callback on the new method.
Most likely your login is also made with a new method (for example, with Devise it would be in SessionsController), so the callback is called again and the value is overwritten (all Devise controllers inherit from your ApplicationController). In this case, you could take out the before_filter callback out of the ApplicationController to the controller where you are handling signing up (would be RegistrationsController with Devise).

Unable to find Twitter uid from Users Table

I've been trying to play around with Omniauth-Twitter and am stuck with an infuriating problem. I'm unable to match my session id to the stored Twitter uid. I used Ryan Bates' screencast to assist me through the walkthrough.
Here's my session controller:
class SessionsController < ApplicationController
def create
auth = request.env["omniauth.auth"]
user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth)
session[:user_id] = user.uid
redirect_to root_url, :notice => "Signed in!"
end
def destroy
session[:user_id] = nil
redirect_to root_url, :notice => "Signed out!"
end
end
Here's my user model:
class User < ActiveRecord::Base
def self.create_with_omniauth(auth)
create! do |user|
user.provider = auth["provider"]
user.uid = auth["uid"]
user.name = auth["info"]["name"]
end
end
end
Here's my application controller that should be able to assign my Twitter persona to the current user variable:
class ApplicationController < ActionController::Base
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
On the front end, I'm trying to essentially display the user name, like so:
<% if current_user %>
Welcome <%= current_user.name %>!
<%= link_to "Sign Out", signout_path %>
<% else %>
<%= link_to "Sign in with Twitter", "/auth/twitter" %>
When I run the application and hit "Sign in with Twitter", I'm getting an error that says:
"ActiveRecord::RecordNotFound. Couldn't find User with id= "
The server's highlighting the current_user method as the error point. Any help would be appreciated? I'll gladly provide more info if needed.
You're confusing the user id, which is automatically assigned by the database, with uid, which is Twitter's id. Either:
Change session[:user_id] = user.uid to session[:user_id] = user.id. Or
Change
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
to
def current_user
#current_user ||= User.find_by(uid: session[:user_id]) if session[:user_id]
end
Personally, I would go for the first option.

Displaying Facebook info of logged out user

Creating a Rails app that interacts heavily with Facebook. I want to users to be able to look at profiles of other users and see information on those users that has been pulled in from Facebook.
I'm using the omniauth-facebook and koala gems. I followed Ryan Bates' Railscasts on Facebook authentication and Facebook Graph API to get where I'm at.
I would prefer not to have to store a user's Facebook info in my database, but that seems to be the only way of doing it, because otherwise, when I try to view the profile page of another user who may have logged out, I get: "Koala::Facebook::AuthenticationError in Profiles#show"
type: OAuthException, code: 190, error_subcode: 467, message: Error validating access token: The session is invalid because the user logged out. [HTTP 400].
Is there a way to bring in this data from Facebook, even if that user has logged out?
Here's the necessary code:
user.rb:
class User < ActiveRecord::Base
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.image = auth.info.image
user.oauth_token = auth.credentials.token
user.oauth_expires_at = Time.at(auth.credentials.expires_at)
user.save!
end
end
def facebook
#facebook ||= Koala::Facebook::API.new(oauth_token)
block_given? ? yield(#facebook) : #facebook
rescue Koala::Facebook::APIError
logger.info e.to_s
nil
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to feed_path
end
def destroy
session[:user_id] = nil
redirect_to root_url
end
end
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
rescue ActiveRecord::RecordNotFound
end
helper_method :current_user
end
profiles_controller.rb
class ProfilesController < ApplicationController
def show
#user = User.find(params[:id])
end
end
profiles_helper.rb:
module ProfilesHelper
def facebook_profile_info
#user.facebook.get_object("#{#user.uid}",fields:'gender, locale, bio, birthday,
picture, relationship_status')
end
end
Also, if you have suggestions on better ways to accomplish these tasks than the trajectory I'm headed in, I welcome your suggestions.

Resources