0
votes

I try to install FB Connect on my website ChercheAvocat (Rails 3), but after clicking on the link "Sign in with Facebook" I have an error " The parameter app_id is required". URL :https://www.facebook.com/dialog/oauth?client_id=&display=popup&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcustomers%2Fauth%2Ffacebook%2Fcallback&response_type=code&scope=email&state=fbe0db878d8d47b896aa18f40e00d5f1e117dde9bef55de9

I have created the app (Chercheavocat-login) on Developers.Facebook and I have added the ID into the code.

Can you help me ?

routes.rb

ChercheAvocat::Application.routes.draw do

    devise_for :customers, :controllers => {
        :omniauth_callbacks => "customers/omniauth_callbacks"
      }

      root :to => 'home#index'

initializers/devise.rb

require "omniauth-facebook"
Devise.setup do |config|

  config.secret_key = 'xxxxxxxxx'

  config.omniauth :facebook,
      ENV['FACEBOOK_KEY'],
      ENV['FACEBOOK_SECRET'],
      :scope => 'email',
      :display => 'popup',
      :provider_ignores_state => true

views/questions/no_answers.html.erb

<script>
  window.fbAsyncInit = function() {
    FB.init({
      appId      : '1400942343542259',
      xfbml      : true,
      version    : 'v2.2'
    });
  };

  (function(d, s, id){
     var js, fjs = d.getElementsByTagName(s)[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement(s); js.id = id;
     js.src = "//connect.facebook.net/en_US/sdk.js";
     fjs.parentNode.insertBefore(js, fjs);
   }(document, 'script', 'facebook-jssdk'));
</script>

models/customer.rb

require 'digest/sha1'

class Customer < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation, :remember_me
  MIN_PASSWORD_LENGTH = 6
  MAX_PASSWORD_LENGTH = 32
  SALT = '")cvvvvvv("f!dsqf!!rffffqf/qdddf+/79dddd54'

  devise :database_authenticatable, :registerable,
    :recoverable, :rememberable, :confirmable,
    :omniauthable, :omniauth_providers => [:facebook]

  validates_presence_of :civility, :first_name, :last_name, :email
  validates :email, :email => true
  validates_uniqueness_of :email
  validates_confirmation_of :email
  validates_presence_of :password, :on => :create
  validates_length_of :password, :within => MIN_PASSWORD_LENGTH..MAX_PASSWORD_LENGTH
  validates_confirmation_of :password
  validates_presence_of :zip_code, :city, :on => :update

  acts_as_mappable
  acts_as_url :id_full_name, :sync_url => true # uses attributes :url

  attr_accessor :password

  has_many :contact_requests, :dependent => :nullify
  has_many :questions, :dependent => :nullify
  has_many :subscriptions, :dependent => :destroy

  before_validation(:encrypt_password, :on => :create)
  before_save       :ensure_geolocation, :setup_modifications
  after_create      :sync_subs
  before_update     :check_sub_related_changes
  after_update      :sync_sub_related_changes

  CIVILITIES_ORDERED = ['mr', 'mrs', 'miss']

  scope :for_search, lambda { |params|
    custom_scope = self.scoped
    (params[:search] || '').split(/\s+/).each { |word|
      custom_scope = custom_scope.where(
        'first_name like ? or last_name like ? or email like ?',"%#{word}%", "%#{word}%", "%#{word}%"
      )
    }
    custom_scope
  }

  def self.civilities(format = :short)
    CIVILITIES_ORDERED.map { |code| [I18n.t(format, :scope => "civilities.#{code}"), code] }
  end

  [:short, :long].each do |code|
    define_method "#{code}_civility" do
      I18n.t(code, :scope => "civilities.#{civility}")
    end
  end

  def full_name
    "#{first_name.strip.titleize} #{last_name.strip.titleize}"
  end

  def id_full_name
    "#{id}-#{full_name}".to_url
  end

  def to_param
    url
  end

  def geolocated?
    lat.present? && lng.present?
  end

  def greeting_name
    "#{short_civility} #{last_name.strip.titleize}"
  end

  def has_sub?(kind)
    subscriptions.kind(kind).count > 0
  end

  def obfuscation
    [long_civility, last_name.present? && "#{last_name.strip[0, 1].mb_chars.upcase}." || nil].compact.join(' ')
  end

  # Set a new activation_code to persist password until email sent
  def regenerate_password!
    self.password = self.class.random_pronouncable_password
    save(validate: false)
  end

  def result_name
    "#{short_civility} #{last_name.titleize}"
  end

  # Designed to be used from customer forms (either back or authenticated front).
  # kinds is a complete list of subscribed kinds.
  # DEV NOTE: this impacts the DB immediately and may raise AR exceptions.
  def subscription_kinds=(kinds)
    if new_record?
      @_required_kinds = kinds
      return
    end
    Subscription.transaction do
      self.subscriptions = subscriptions.select { |s| kinds.include?(s.kind.to_s) }
      subscriptions.update_all :revocation => nil
      subscriptions.update_all({:confirmation => Time.zone.now}, :confirmation => nil)
      kinds_left = subscriptions.map { |s| s.kind.to_s }
      (kinds - kinds_left).each do |k|
        s = subscriptions.create! :email => email, :kind => k.to_sym
        s.activate!
      end
    end
  end

  def self.authenticate(email, pass)
    customer = find_by_email(email)
    if customer
      expected_hashed_pwd = generate_encrypted_password(customer.salt, pass)
      customer = nil unless expected_hashed_pwd == customer.hashed_password
    end
    customer
  end

  def self.find_for_facebook_oauth(auth, signed_in_resource=nil)

    customer = Customer.where(:provider => auth.provider, :uid => auth.uid).first
    if customer
      return customer
    else
      registered_customer = Customer.where(:email => auth.info.email).first
      if registered_customer
        return registered_customer
      else
        customer = Customer.create(name:auth.extra.raw_info.name,
                            provider:auth.provider,
                            uid:auth.uid,
                            email:auth.info.email,
                            password:Devise.friendly_token[0,20],
                          )
      end
    end
  end

  private
  def check_sub_related_changes
    @email_changed = email_changed?
    true
  end

  def encrypt_password
    self.salt = object_id.to_s + rand.to_s
    self.hashed_password = self.class.generate_encrypted_password(salt, password)
  end

  def ensure_geolocation
    return true if geolocated?
    base = [address1, address2, zip_code, city, 'France'].reject(&:blank?).map(&:strip).join(' ')
    return true if base.blank?
    geocoding = Geokit::Geocoders::MultiGeocoder.geocode(base)
    auto_geocoded = geocoding.success? && geocoding.accuracy >= 4 # Town level
    self.lat, self.lng = geocoding.lat, geocoding.lng if auto_geocoded
    true
  end

  def self.generate_encrypted_password(salt, pass)
    Digest::SHA1.hexdigest(salt.to_s + SALT.gsub(/\W/, '').downcase + pass.to_s)
  end

  def self.random_pronouncable_password(size = 4)
    c = %w(b c d f g h j k l m n p qu r s t v w x z ch cr fr nd ng nk nt ph pr rd sh sl sp st th tr)
    v = %w(a e i o u y)
    (1..size * 2).inject('') { |acc, n| acc << (0 == n % 2 ? c[rand * c.size] : v[rand * v.size]) }
  end

  def setup_modifications
    encrypt_password if password.present?
    # self.password = nil # DEV NOTE: we need to keep it in memory for forgotten-password e-mail notifications
    # Subscription.find_all_by_email(self.email).each { |s| s.customer = self }
  end

  def sync_subs
    Subscription.update_all({ :customer_id => id }, :email => email)
    self.subscription_kinds = @_required_kinds unless @_required_kinds.blank?
  end

  def sync_sub_related_changes
    changes = {}
    changes[:email] = email if @email_changed
    subscriptions.update_all(changes) unless changes.empty?
  end

  def validate
    if hashed_password.blank?
      errors.add(:password, :blank)
    end
    if !email.blank? && !email_confirmation.blank? && email != email_confirmation
      errors.add(:email, :confirmation)
    end
    errors.add(:terms_of_use, :accepted) unless terms_of_use
  end
end

controllers/customers/omniauth_callbacks_controller.rb

class Customers::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def facebook
     @customer = Customer.find_for_facebook_oauth(request.env["omniauth.auth"], current_customer)
     if @customer.persisted?
      sign_in_and_redirect @customer, :event => :authentication #this will throw if @customer 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_customer_registration_url
    end
  end

end
1
Now please change the production values of config.secret_key in devise initializer and of SALT in the Customer class... They should be stored in environment variables ;)Olivier Lance
Does the error occur when loading the Facebook page to sign in, or when returning from it to your site? Please include an excerpt of the view to show how you create the signin link!Olivier Lance
The error occurs when I click to the link. Here it is the UrL :facebook.com/dialog/…Mathieu Bernard

1 Answers

1
votes

There's no app_id parameter in the URL that is generated by OmniAuth to sign in with Facebook.

Have you configured your app correctly? You should have a specific line in your Devise initializer concerning OmniAuth, something like:

config.omniauth :facebook, "APP_ID", "APP_SECRET"

The APP_ID and APP_SECRET are found in the details page of the Facebook App you created. Once again, keep them private. You should not put them directly in your code but rather use the Figaro gem and environment variables.

Full details on the implementation of OmniAuth Facebook with Devise are there: https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview

Update
So this was in fact done, but have you verified these environment variables are really set to the correct values?
I see you're running your server on localhost, presuming a dev environment then; have you added those environment variables to your, well, environment?