Search code examples
ruby-on-railsdeviseomniauthomniauth-facebook

RuntimeError in CallbacksController#facebook after successful authentication with Devise / Omniauth Rails


I am setting up devise with omniauth facebook gem to build a signup system. Users can either register with email or connect with their facebook account. If an email registered user signs in with the facebook account, I check if the email address is already registered and connect these two accounts, and then log the user in.

This whole scenario works already. The user entry is updated with the new omniauth data of facebook. I get this error, when facebooks sends the callback (data from facebook is saved to db successfully):

RuntimeError in CallbacksController#facebook

Could not find a valid mapping for true

Extracted source (around line #5):

class CallbacksController < Devise::OmniauthCallbacksController
  # Define each provider for omniauth here (def twitter...)
  def facebook
    @user = User.from_omniauth(request.env["omniauth.auth"])
    sign_in_and_redirect @user
  end
end

user.rb modell looks like this:

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :omniauthable, :omniauth_providers => [:facebook]

  def self.from_omniauth(auth)
    user = where(provider: auth.provider, uid: auth.uid).first
    unless user
      user = where(email: auth.info.email).first_or_initialize
      user.provider = auth.provider
      user.uid = auth.uid
      user.email = auth.info.email
      user.name = auth.info.name
      user.nickname = auth.info.nickname
      user.first_name = auth.info.first_name
      user.last_name = auth.info.last_name
      user.location = auth.info.location
      user.description = auth.info.description
      user.image = auth.info.image
      user.phone = auth.info.phone
      user.urls = auth.info.urls
      user.password = Devise.friendly_token[0,20]
      user.save!
    end
  end
end

routes.rb

Rails.application.routes.draw do
  devise_for :users, :controllers => { :omniauth_callbacks => "callbacks" }
  resources :auctions do
    resources :comments
  end

  root 'welcome#index'
  get '/', :to => 'welcome#index'
end

Solution

  • Your self.from_omniauth method in your User class is returning true, instead of a User model. It's because the return value of a method in Ruby is the result of the last line evaluated, and in this case user.save! is the last line that runs. The "Could not find a valid mapping for true" error is a result of passing true into sign_in_and_redirect; you can see in the Devise source that the first argument is passed into Devise::Mapping.find_scope!.

    The solution's simple - make sure that you're returning your user model in from_omniauth:

    def self.from_omniauth(auth)
        user = where(provider: auth.provider, uid: auth.uid).first
        unless user
          user = where(email: auth.info.email).first_or_initialize
          user.provider = auth.provider
          user.uid = auth.uid
          user.email = auth.info.email
          user.name = auth.info.name
          user.nickname = auth.info.nickname
          user.first_name = auth.info.first_name
          user.last_name = auth.info.last_name
          user.location = auth.info.location
          user.description = auth.info.description
          user.image = auth.info.image
          user.phone = auth.info.phone
          user.urls = auth.info.urls
          user.password = Devise.friendly_token[0,20]
          user.save!
        end
        user # Make sure we return the user we constructed.
      end