Hi I am using devise and omniauth to autenticate facebook login, but I get the following error:
Can't mass-assign protected attributes: token
app/models/user.rb:20:in `apply_omniauth'
app/controllers/authentications_controller.rb:19:in `create'
this the user model:
class User < ActiveRecord::Base
# The relationship between the User and Authentication model
has_many :authentications, :dependent => :delete_all
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me,
# attr_accessible :title, :body
def apply_omniauth(auth)
# In previous omniauth, 'user_info' was used in place of 'raw_info'
self.email = auth['extra']['raw_info']['email']
authentications.build(:provider => auth['provider'], :uid => auth['uid'], :token => auth['credentials']['token'])
end
end
This is my authentication controller:
class AuthenticationsController < ApplicationController
def index
#authentications = current_user.authentications if current_user
end
def create
auth = request.env["omniauth.auth"]
# Try to find authentication first
authentication = Authentication.find_by_provider_and_uid(auth['provider'], auth['uid'])
if authentication
# Authentication found, sign the user in.
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, authentication.user)
else
# Authentication not found, thus a new user.
user = User.new
user.apply_omniauth(auth)
if user.save(:validate => false)
flash[:notice] = "Account created and signed in successfully."
sign_in_and_redirect(:user, user)
else
flash[:error] = "Error while creating a user account. Please try again."
redirect_to root_url
end
end
end
def destroy
#authentication = Authentication.find(params[:id])
#authentication.destroy
redirect_to authentications_url, :notice => "Successfully destroyed authentication."
end
end
Could somebody explain why I get this error, and how I fix it?
Adding :token to the attr_accessible line in the authentication model should do the trick.
Related
In my Rails app, I have user and authorization tables to handle users and auth data. I set up both Devise and Omniauth to use Twitter to sign up, it redirects to Twitter, but after returning to my app, it gives an error like:
NoMethodError at /users/auth/twitter/callback
undefined method `authorizations' for #<Class:0xbdc8100>
In which side, did I go wrong and how can I fix this issue?
Here are related parts: omniauth_callbacks_controller.rb:
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def all
user = User.authorizations.from_auth(auth_hash)
if user.persisted?
flash.notice = "Signed in!"
sign_in_and_redirect user
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
alias_method :twitter, :all
protected
def auth_hash
request.env['omniauth.auth']
end
end
authorization.rb:
class Authorization < ActiveRecord::Base
attr_accessible :uid, :provider
belongs_to :user
def self.from_auth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
end
end
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, :omniauth_providers => [:twitter, :facebook]
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :name
# attr_accessible :title, :body
has_many :authorizations, dependent: :destroy
end
Your issue is in this line...
user = User.authorizations.from_auth(auth_hash)
You call authorizations on the class User, but as an attribute it needs to be called on an instance of the User class, i.e. a specific user.
So I've got Omniauth with Twitter validation functioning at about 95%, in other-words it's almost fully functional. When clicking the | Sign-In with Twitter | button on my app, it re-directs me to twitter where I am then prompted to enter my Twitter credentials, and then re-directed back to the app.
However, instead of logging me in after the Twitter auth process I get the following error on the Sign-in page:
1 error prohibited this user from being saved:
Email can't be blank
How can I get Omniauth to re-direct me to the logged in page in my case the /posts page? And why is it producing such an error, when Omniauth is supposed to authorize me via Twitter validation?
User model:
class User < ActiveRecord::Base
has_many :authentications
# Include default devise modules. Others available are:
# :token_authenticatable, :lockable, :timeoutable and :activatable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation
# attr_accessible :title, :body
def apply_omniauth(omniauth)
self.email = omniauth['info']['email'] if email.blank?
authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'])
end
def password_required?
(authentications.empty? || !password.blank?) && super
end
end
Authentications Controller:
class AuthenticationsController < ApplicationController
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, authentication.user)
elsif current_user
current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
else
user = User.new
user.apply_omniauth(omniauth)
if user.save
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omniauth.except('extra')
redirect_to new_user_registration_url
end
end
end
end
Registrations Controller:
class RegistrationsController < Devise::RegistrationsController
def create
super
session[:omniauth] = nil unless #user.new_record?
end
private
def build_resource(*args)
super
if session[:omniauth]
#user.apply_omniauth(session[:omniauth])
#user.valid?
end
end
end
I just remembered, the reason is actually simple, twitter doesn't return email in the omniauth request so it redirects you to the new user registration page and you have to fill in your email.
https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema
furthermore this could be of help
Skip email validation for omniauth-twitter on devise 2.0
I am getting undefined method `[]' for nil:NilClass
NoMethodError in Users::OmniauthCallbacksController#facebook
undefined method `[]' for nil:NilClass
Rails.root: /home/krishna/picer
Application Trace | Framework Trace | Full Trace
app/models/user.rb:13:in `find_for_facebook_oauth'
app/controllers/users/omniauth_callbacks_controller.rb:4:in `facebook'
This is my user.rb code
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable:omniauthable
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"])
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"]["user_hash"]
user.email = data["email"]
end
end
end
end
And omniauth callbacks controller code
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model
#user = User.find_for_facebook_oauth(request.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
def passthru
render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
end
end
I am getting error line four user.rb I used a example for this https://github.com/ryanatwork/devise-omniauth
You should get the user email with access_token['info']['email'] instead of access_token['extra']['user_hash']['email'].
I don't think the data in access_token['extra'] follows a convention like the rest, so you should be extra careful and test if the property you're trying to access exists. It's not that important if you only have one provider, but it is if you have several of them.
My app is set up so that if a user signs in with Oauth or Openid, they don't have to confirm their email address. However, Devise is still sending email confirmations. When I call User.skip_confirmation! I get an undefined method error. My model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :recoverable, :rememberable,
:trackable, :validatable, :confirmable, :lockable, :token_authenticatable, :omniauthable
attr_accessible :username, :email, :password, :password_confirmation, :remember_me
validates_presence_of :username
validates_uniqueness_of :username, :case_sensitive => false
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
#User.skip_confirmation!
User.create!(:username => data.name, :email => data.email, :password => Devise.friendly_token[0,20])
end
end
def skip_confirmation!
self.confirmed_at = Time.now
end
end
My Controller:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
#user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
#user.skip_confirmation!
if #user.persisted?
sign_in #user
#fname = #user.username
redirect_to root_path, :flash => { :success => "Welcome #{#fname}!" }
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
thanks for any help.
You need to skip confirmation before you create the User objects and its persisted to the database. So the user creation part of your method would look like
user = User.new(:username => data.name, :email => data.email, :password => Devise.friendly_token[0,20])
user.skip_confirmation!
user.save
If you're updating a user record, make sure to use skip_reconfirmation! (mind the re)
I have a bit of code here where users can login via their twitter account. the problem here is, how can i skip email confirmation for user the sign up from external services like twitter. i am using devise and i do not know how to skip the email confirmation for this type of users. my code sample is as follows
class AuthenticationsController < ApplicationController
# GET /authentications
# GET /authentications.json
def index
#authentications = current_user.authentications if current_user
end
# POST /authentications
# POST /authentications.json
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
flash[:notice] = "Signed in successfully"
sign_in_and_redirect(:user, authentication.user)
elsif current_user
current_user.authentications.create!(:provider => omniauth['provider'], :uid => ['uid'])
flash[:notice] = "Authentication successful"
redirect_to authentication_url
else
user = User.new
user.apply_omniauth(omniauth)
if user.save
flash[:notice] = "Signed in successfully"
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omniauth.except('extra')
redirect_to new_user_registration_url
end
end
rescue Exception => e
# Just spit out the error message and a backtrace.
render :text => "<html><body><pre>" + e.to_s + "</pre><hr /><pre>" + e.backtrace.join("\n") + "</pre></body></html>"
end
# DELETE /authentications/1
# DELETE /authentications/1.json
def destroy
#authentication = current_user.authentications.find(params[:id])
#authentication.destroy
respond_to do |format|
format.html { redirect_to authentications_url }
format.json { head :ok }
end
end
end
my registration controller is as follows
class RegistrationsController < Devise::RegistrationsController
def create
super
session[:omniauth] = nil unless #user.new_record?
end
private
def build_resource(*args)
super
if session[:omniauth]
#user.apply_omniauth(session[:omniauth])
#user.valid?
end
end
end
and my user model is below
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :lockable, :timeoutable and
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :about, :facebook_username, :twitter_username, :icon, :admin
validates_uniqueness_of :username
has_attached_file :icon, :styles => {:thumb => "64x64#"}, :default_url => 'icon_:style.png'
validates_attachment_content_type :icon, :content_type => ['image/jpeg', 'image/png', 'image/gif']
validates_attachment_size :icon, :less_than => 1.megabyte
ajaxful_rater
has_many :authentications
validates_presence_of :username
def apply_omniauth(omniauth)
self.email = omniauth['user_info']['email'] if email.blank?
self.name = omniauth['user_info']['name'] if name.blank?
self.image = omniauth['user_info']['image'] if image.blank?
authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'])
end
def password_required?
(authentications.empty? || !password.blank?) && super
end
end
my like to the auth url is below
<a href="/auth/twitter" class="auth_provider">
<%= image_tag "twitter_64.png", :size => "64x64", :alt => "Twitter" %>
</a>
my routh is like this
match 'auth/:provider/callback' => "authentications#create"
Whenever you want to skip confirmation for Devise period, just use the following before the user.save...
user.skip_confirmation!
So basically, in your create controller action, if it detects omniauth logic, then call that.
This could be achieved by setting the confirmed_at attribute, which Devise sets internally to mark an account as confirmed:
user.update(
confirmed_at: Time.now.utc,
confirmation_token: nil
)