I am trying to enable users with the option to signing in through Facebook.
My authentication in managed by Devise and I'm using the Omniauth-Facebook gem.
When I try to login the user through Facebook in my devise views (session/new.html.erb), the user is logged in successfully.
new.html.erb
<div><%= link_to image_tag('facebook_login.png'), user_omniauth_authorize_path(:facebook) %></div>
However when I try to sign in the user in a different controller (using the exactly same code as above) the page doesn't really respond and I'm stuck with the following request on my log:
Started GET "/users/auth/facebook" for 127.0.0.1 at 2014-06-07 21:14:41 -0300
I, [2014-06-07T21:14:41.311370 #4592] INFO -- omniauth: (facebook) Request phase initiated.
Does Devise only allow users to sign in with facebook through a single controller or is there some other change I need to make to the link_to path?
I think you configure omniauth separate from devise(in omniauth initializers), but still using devise for login(devise paths for omniauth).
I leave the steps for get it working with devise work-flow
Gemfile
gem 'devise'
gem 'omniauth-facebook'
config/initializers/devise.rb
//add this for tell devise the omniauth configuration for facebook
config.omniauth :facebook, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_SECRET']
config/enviroments/development.rb
//your secrets for development, is useful have it this way because you can use different applications for your rails enviroments
ENV['FACEBOOK_APP_ID'] = 'xxxxxxxxxxx';
ENV['FACEBOOK_SECRET'] = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
routes.rb
//override the controller for omniauth callbacks, we will define this later, this is our custom behavior for dealing with what omniauth hash will return to us
devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }
Ok this is the basic, now we need to have our custom callbacks that defines what to do with the information that facebook gives to us.
I don't have a working example for this, my callbacks are actually really complicated because have lots of behaviors, but i will try to explain by memory.
Facebook and all omniauth strategies will return an uid, we are gonna identify our users by the provider and his uid (user id), so we are gonna add this params to our user model and do the corresponding migrations, these are both strings.
rails g migration AddOmniauthToUsers uid provider
rake db:migrate
The next step is configure our omniauth callback
controllers/users/omniauth_callbacks_controller.rb (note that is in a new users folder)
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
//this inheritance from Devise::OmniauthCallbacks so we will have all his methods
#uncomment the next line to see the hash from facebook, utile for debugging to see if we are getting a connection to facebook or not.
#raise request.env["omniauth.auth"].to_yaml
def all
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
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => user.provider.titleize
else
session["devise.user_attributes"] = request.env["omniauth.auth"] #create a cookie with omniauth hash to give it another try, for example in a already register email
redirect_to new_user_registration_url
end
end
//alias method is for have the same method for various strategies(google, twitter, etc)
alias_method :facebook, :all
end
Basically, when we click the facebook connection we get this hash request.env["omniauth.auth"] with all the omniauth params and now we need to define our from_omniauth method for dealing with in our user model
models/user.rb
def self.from_omniauth(auth)
where(auth.slice(:provider, :uid)).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
end
end
def self.new_with_session(params, session)
if session["devise.user_attributes"]
new(session["devise.user_attributes"], without_protection: true) do |user|
user.attributes = params
user.valid?
end
else
super
end
end
def password_required?
super && self.provider.blank?
end
def update_with_password(params, *options)
if encrypted_password.blank?
update_attributes(params, *options)
else
super
end
end
def has_no_password?
self.encrypted_password.blank?
end
Here we have 4 new methods, from_omniauth will create a new user with the omniauth params from the hash (you can see the omniauth hash for facebook in omniauth facebook gem), here you should save another values like image_link or username if you have it your user model, i like to call from here another model user_profile with all these data a trying to leave devise with his own things.
Next we have new with session, that use the content of the cookie that we save in case of error in our callback
password_required? and update_with_password are both devise methods that we need to override, this one grab our new behavior for the cases with omniauth provider and no password defined, you could use has_no_password? in your forms for displaying password fields just when need it(update user attributes without a password).
I hope it helps, when you understand the work-flow is really easy to customize the behaviors, but sadly most guides just let you copy-paste the code.