简体   繁体   中英

Rails 4 Facebook authentication with Devise and Omniauth

I'm trying to implement authentication from Facebook in my app, for a Customer model. I had already done an authentication for Customers with Devise. I had followed this guide . In the initializer devise.rb I added this row:

config.omniauth :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'], scope: "email"

This is my Customer model:

class Customer < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :omniauthable,
     :recoverable, :rememberable, :trackable #, :validatable

validates_presence_of :name

validates_uniqueness_of :nickname

before_save :complete_nickname

def complete_nickname
  if !self.nickname?
    self.nickname = self.email
  end
end

def facebook
  identities.where( :provider => "facebook" ).first
end

def facebook_client
  @facebook_client ||= Facebook.client( access_token: facebook.accesstoken )
end
end

This is my Identity model, written as specified in the guide:

class Identity < ActiveRecord::Base
  belongs_to :customer

  validates_presence_of :uid, :provider
  validates_uniqueness_of :uid, :scope => :provider

  def self.find_for_oauth(auth)
    identity = find_by(provider: auth.provider, uid: auth.uid)
    identity = create(uid: auth.uid, provider: auth.provider) if identity.nil?
    identity.accesstoken = auth.credentials.token
    identity.refreshtoken = auth.credentials.refresh_token
    identity.name = auth.info.name
    identity.email = auth.info.email
    identity.nickname = auth.info.name.gsub(/\s+/, "")
    identity.image = auth.info.image
    identity.phone = auth.info.phone
    identity.urls = (auth.info.urls || "").to_json
    identity.save
    identity
  end
end

This is my OmniauthCallbackController

class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    generic_callback( 'facebook' )
  end

  def generic_callback( provider )
    @identity = Identity.find_for_oauth env["omniauth.auth"]

    @customer = @identity.customer || current_customer
    if @customer.nil?
      @customer = Customer.create( email: @identity.email || "", nickname: @identity.email || "" )
      @identity.update_attribute( :customer_id, @customer.id )
    end  

    if @customer.email.blank? && @identity.email
      @customer.update_attribute( :email, @identity.email)
    end

    if @customer.persisted?
      @identity.update_attribute( :customer_id, @customer.id )
      # This is because we've created the user manually, and Device expects a
      # FormUser class (with the validations)
      @customer = FormUser.find @customer.id
      sign_in_and_redirect @customer, event: :authentication
      set_flash_message(:notice, :success, kind: provider.capitalize) if is_navigational_format?
      else
        session["devise.#{provider}_data"] = env["omniauth.auth"]
        redirect_to new_customer_registration_url
      end
    end
  end

When I click on Login in with Facebook I am not authenticated, and the app redirects me on the login form. On the server outuput I see that it tried to create a Customer, but then it fails and rollbacks:

  SQL (0.3ms)  UPDATE "identities" SET "accesstoken" = $1, "updated_at" = $2 WHERE "identities"."id" = $3  [["accesstoken", "CAABtFtDqAlwBAEFzpAtw2W2gUTLkpKbtjI4lqKibkIO5kyJSwNYK9TDzDG4NfEoq40oQdUzXxZADRZBwYy319KPobEU6378ULwQ3PtKT46EbEugs4eIdsQyfZC3S8yLIJoPW6uq7ZAF0AnEeepDvl97ajbazsmGsuP22rnK0G1zSTHzoBfPpGZAD4NkgW7Kp5r5TiLhxxEgZDZD"], ["updated_at", "2015-11-24 11:18:28.010402"], ["id", 2]]
  (65.7ms)  COMMIT
  (0.2ms)  BEGIN
  Customer Exists (4.0ms)  SELECT  1 AS one FROM "customers" WHERE  "customers"."nickname" = '' LIMIT 1
  (0.2ms)  ROLLBACK

Maybe it's a validation control that I already had on my Customer object, but I can't figure out what it can be. This is the schema of my Customer:

  create_table "customers", force: :cascade do |t|
    t.string   "email",                   default: "",   null: false
    t.string   "encrypted_password",      default: "",   null: false
    t.string   "name",                    default: "",   null: false
    t.string   "surname",                 default: "",   null: false
    t.string   "nickname",                default: "",   null: false
    ...
  end

Someone can help me? Thanks in advance

Try to this configuration

devise.rb

config.omniauth :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'], scope: 'email', info_fields: 'email, name'

I solved, it seems to work with my Facebook account. I update the omniauth-facebook gem, that maybe was part of the issue, with bundle update omniauth-facebook . I modified the initializer devise.rb in this way (no spaces in scope and info_fields):

`config.omniauth :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'], scope: "email,public_profile", info_fields: 'email,name,first_name,last_name'`

And modified the OmniauthCallbackController:

@customer = Customer.create( email: @identity.email || "", nickname: @identity.nickname || "", 
  name: env["omniauth.auth"]["info"]["first_name"] || "", 
  surname: env["omniauth.auth"]["info"]["last_name"] || "",
  password: "********", password_confirmation: "********" )
  @identity.update_attribute( :customer_id, @customer.id )

Because in my Customer model name and surname are mandatory fields. I still have a problem. I pushed the app on Heroku and I can sign in with my Facebook account and with other users accounts, but for some other users Facebook doesn't return the email.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM