I have successfully implemented login with Facebook using Devise and OmniAuth (built into Devise). Now I need to figure out how to store the user's name in the database so that I can display their name for other users to see that they created/edited records in my application.
I must have copied this code from somewhere, and it looks like it should be working, but it is definitely not saving the name in the database:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#user = User.find_for_facebook_oauth(env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.facebook_data"] = env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable, :lockable and :timeoutable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token['extra']['user_hash']
if user = User.find_by_email(data['email'])
if user.first_name != data['first_name']
user.update_attributes :first_name => data['first_name']
end
user
else # Create an user with a stub password.
User.create!(:email => data['email'],
:first_name => data['first_name'],
: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']['user_hash']
user.email = data['email']
end
end
end
end
What am I doing wrong?
Andrew, as written here: the method attr_accessible makes the attribute available for mass assignment.
You should put it like this:
attr_accessible :email, :password, :password_confirmation, :remember_me, :first_name
Related
I am using omniAuth + facebook authentication
#user = User.find_for_facebook_oauth(request.env["omniauth.auth"])
if #user.persisted?
#user.reload
sign_in #user, :event => :authentication
redirect_to after_sign_in_path_for(#user)
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
After facebook signIn, user lands on root url, when try to open /account/profile gives unauthorized error. But after a few seconds /account/profile becomes accessible. Why there is a delay here?
class AccountController < ApplicationController
layout false
def profile
if !current_user
render status: 401
return
end
end
I can not successfully confirm my user. Many posts have been made about an invalid token but most of them were due to the changes from Devise 2 to Devise 3. That is not my issue.
I over rid my user confirmation using
https://github.com/plataformatec/devise/wiki/How-To:-Override-confirmations-so-users-can-pick-their-own-passwords-as-part-of-confirmation-activation
app/controllers/confirmations_controller.rb
class ConfirmationsController < Devise::ConfirmationsController
# Remove the first skip_before_filter (:require_no_authentication) if you
# don't want to enable logged users to access the confirmation page.
skip_before_filter :require_no_authentication
skip_before_filter :authenticate_user!
# PUT /resource/confirmation
def update
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
#confirmable.attempt_set_password(params[:user])
if #confirmable.valid?
do_confirm
else
do_show
#confirmable.errors.clear #so that we wont render :new
end
else
self.class.add_error_on(self, :email, :password_already_set)
end
end
if !#confirmable.errors.empty?
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
do_show
else
do_confirm
end
end
if !#confirmable.errors.empty?
self.resource = #confirmable
render 'devise/confirmations/new' #Change this if you don't have the views on default path
end
end
protected
def with_unconfirmed_confirmable
original_token = params[:confirmation_token]
confirmation_token = Devise.token_generator.digest(User, :confirmation_token, original_token)
#confirmable = User.find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
if !#confirmable.new_record?
#confirmable.only_if_unconfirmed {yield}
end
end
def do_show
#confirmation_token = params[:confirmation_token]
#requires_password = true
self.resource = #confirmable
render 'devise/confirmations/show' #Change this if you don't have the views on default path
end
def do_confirm
#confirmable.confirm!
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, #confirmable)
end
end
The HTML that is sent to the user for confirmation in the email is
<%= link_to 'Confirm', confirmation_url(#resource, :confirmation_token => #token) %>
The problem I am having is that initially the user is being sent to the show page since I by-passed the need for a password and no confirmation is made but no error is shown.
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
if #confirmable.has_no_password?
do_show
else
do_confirm
end
end
When I removed the if statement to leave only
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
do_confirm
end
The user is now sent to the confirmation New page but with the error
Confirmation token is invalid
My routes are
as :user do
patch '/user/confirmation' => 'confirmations#update', :via => :patch, :as => :update_user_confirmation
end
devise_for :users, :controllers => { :confirmations => "confirmations", registrations: "registrations" }
devise_scope :user do
authenticated :user do
root :to => 'devise/sessions#destroy', as: :authenticated_root
end
unauthenticated :user do
root :to => 'devise/registrations#new', as: :unauthenticated_root
end
end
Why is this token not working correctly?
I finally got "sign in with Facebook" to work with devise/omniauth, but when a user is signed in, the "sign in with facebook" link doesn't change to "sign out" and/or there is no visible sign out option.
here is my route.rb file
devise_for :users, :controllers => {:omniauth_callbacks => "users/omniauth_callbacks", :registrations => 'registrations'}, :path_names => { :sign_in => 'login', :sign_out => 'logout' } do
get 'login' =>'devise/sessions#new', :as => :new_user_session
post 'login' => 'devise/sessions#create', :as => :user_session
get 'signup' => 'registrations#new', :as => :new_user_registration
get 'signout' => 'devise/sessions#destroy', :as => :destroy_user_session
end
and here is the user file
class User < ActiveRecord::Base
devise :omniauthable, :omniauth_providers => [:facebook]
def self.find_for_facebook_oauth(auth, signed_in_resource=ni)
user = User.where (:provider => auth.provider, :uid => auth.uid).first
unless user
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where
user = User.create(name:auth.extra.raw_info.name,
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20]
)
end
user
end
end
session controller:
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_path
end
def destroy
session.delete[:user_id] = nil
redirect_to root_path
end
end
Omniauth_callbacks_controller:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
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"], current_user)
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
end
and lastly, the application layout
<% if current_user %>
Signed in as <strong><%= current_user.name %></strong>!
<%= link_to "Sign out", destroy_user_session, id: "sign_out" %>
<% else %>
<li><%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %></li>
<% end %>
Not quite sure waht i keep doing wrong or why I'm having a hard time looking for the answer, so I thought it might be easier to just put up the code. I"m new at this, so any help is appreciated. Thanks!
Short Answer - current_user is nil so your not actually logged in.
If "Sign in with Facebook" is still showing, and Sign Out is not showing, i'm assuming the "Signed in as" Is also not showing.
This is most likely due to this current_user not being set, so this line is failing
<% if current_user %>
and so this code gets triggered
<li><%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %></li>
So I am assuming current_user is not actually getting set, therefore you're not really logged in. You can add the devise controller filter authenticate_user! to see if you're logged in or not. Something like
class ApplicationController
before_filter :authenticate_user!
end
Which will redirect you if you are not logged in.
Use Pry To Find Out
Pry is a great tool for debugging these kinds of things
To debug with pry, you would add to your Gemfile
group :development do
gem "pry", "~> 0.9.12.4"
end
and run bundle install
you can then add this to your code
<%= binding.pry %>
<% if current_user %>
Signed in as <strong><%= current_user.name %></strong>!
<%= link_to "Sign out", destroy_user_session, id: "sign_out" %>
<% else %>
<li><%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %></li>
<% end %>
Go through your normal Login with Facebook workflow, and the Rails Server in your terminal will 'stop' at the `<% binding.pry %> and allow you to run commands, including checking variables.
In the terminal your rails console is running in, you should see something like
current_user ? "There is a current user set" : "No current user is set"
You may also just run
current_user
but the former has more verbose output. Some other useful debugging commands include
help
whereami
exit
!!!
i am designing this little application which consists of Users, Activities,Comments and Notifications.
Users has many activities, Comments and notifications. the Activity was designed following ryan bates tutorial of PublicActivity from scratch 407-activity-feed-from-scratch. and also each Activity has many Comments. for the Notifications, it also belongs to the user in such a way that when a Comment is created, a notification is generated, so far my code includes
User Model
class User < ActiveRecord::Base
has_many :comments, :dependent => :destroy
has_many :notifications, :dependent => :destroy
has_many :activities, :dependent => :destroy
end
Activity Model
belongs_to :user
belongs_to :trackable, polymorphic: true
has_many :comments
has_many :notifications
default_scope :order => "activities.created_at DESC"
#scope :recent, where(published_at: Time.now - 2.weeks)
end
Comment Model
class Comment < ActiveRecord::Base
belongs_to :activity
belongs_to :user
default_scope :order => "comments.created_at DESC"
end
Notification Model
class Notification < ActiveRecord::Base
belongs_to :user
belongs_to :activity
end
in the controller,
Comments controller i have tried this
def new
#comment = Comment.new
end
def create
#activity = Activity.find(params[:activity_id])
#comment = #activity.comments.create!(comment_params)
#comment.user = #user
#comment.save
#users= User.joins(:comments).where(comments: {id: #activity.comment_ids}).push(#activity.user).uniq_by {|a| a[:user_id]}
#users.each do |user|
Notification.create(activity_id:#activity, user_id: user.id)
end
redirect_to user_path(current_user)
end
Activity Controller
def index
#activities = Activity.all(:include => :comments, :order => "created_at DESC")
#comments = Comment.find(:all, :order => "created_at DESC")
#comment = Comment.new
#comment.user = current_user
end
Notification Controller
def index
#notifications = current_user.notifications
end
WHAT I WANT TO ACHIEVE IS, NOTIFICATIONS SHOULD BE Created And Available To:
the owner of the activity cos each activity belongs to a particular user i.e when a new comment is created by another user, a notification is available to the owner of the activity.
SHOULD NOT! be available to the new commenter i.e the user that as just commented on the activity but available to owner of the activity
Available to all previous commenters i.e every user that as commented on that activity
and the owner of the activity
. for now my code above does not work and also is there anything am doing wrong with the model and controllers?
basically what i will do is get all Users that commented on the activity with the following
##Activity model
has_many :comments
has_many :users, -> {uniq}, through: :comments
So now your new comment create action will look like this
def create
#activity = Activity.find(params[:activity_id])
#comment = #activity.comments.create!(comment_params)
#comment.user = #user
#comment.save
#users= #activity.users.where("id NOT IN (?)", [#activity.user.id, #comment.user])
## Lets create a notification for all those who created a comment in this activity
#users.each do |user|
Notification.create(activity:#activity, user: user)
end
## Lets create a notification for the owner activity
Notification.create(activity:#activity, user: #activity.user)
redirect_to user_path(current_user)
end
This code is not optimized but can help you out
It seems that I cannot save users from my database even though omniauth controller confirms successful login. I followed the instructions from the omniauth wiki: https://github.com/plataformatec/devise/wiki/OmniAuth%3A-Overview
user.rb:
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, :omniauthable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :encrypted_password, :provider, :uid
attr_accessor :email, :password, :password_confirmation, :remember_me, :provider, :uid, :name
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.email = auth.info.email
user.encrypted_password = Devise.friendly_token[0,20]
user.save!
end
end
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
user = User.create(
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
encrypted_password:Devise.friendly_token[0,20]
)
#user.ensure_authentication_token!
#added extra to create authentication for user
user.save
end
user
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"] if user.email.blank?
end
end
end
end
omniauth_callback_controller.rb:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def passthru
render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
# Or alternatively,
# raise ActionController::RoutingError.new('Not Found')
end
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"], current_user)
##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"]
#redirect_to new_user_registration_url
redirect_to messages_url
end
end
end
I've tried logging in with the default devise controller and it goes thru the users database.And since I cannot store to database, I cannot also get the uid.
Through creating a new application from a different approach: http://supriya-surve.blogspot.com/2012/06/rails-3-devise-omniauth-facebook.html, i found the solution.
Update user.rb find_for_facebook_auth:
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
unless user
user = User.create(
provider:auth.provider,
uid:auth.uid,
email:auth.info.email,
password:Devise.friendly_token[0,20]
)
end
user
end
Then the attr_accessible of user.rb:
attr_accessible :email, :password, :password_confirmation, :remember_me, :provider, :uid, :oauth_token, :oauth_expires_at
Upon successful login, user is saved to database.
I also had the same problem when I decided to add Tumblr authentication to an existing Devise user model. Turns out, Devise was throwing an error on the index_users_on_email database index.
Nobody on the net had that problem, so I thought I'd throw it out there. I spent a long time trying to figure it out. Glad you got it solved :)