This post is just a simple straightforward description of Omniauth:Overview , intended for the beginners , those who wants to try the OmniAuth for the first time with Facebook , Google & Twitter. This post will cover all the basic information i.e From creating the app in Facebook, Google & Twitter for getting the secret key To connect these Apps to your Rails Application.
Things in common :
add Devise & omniauth gem to your Gemfile.
gem 'omniauth'
gem 'devise'
First of all install Devise into your application.
rails generate devise:installYou have to create user model using devise ,
rails g devise userand after that we need to add 2 more columns i.e "uid" and 'provider' to our user model.
rails g migration AddColumnsToUsers provider:string uid:string rake db:migrate
We have done with the things that is common in authentication using facebook,Google, Twitter.
We will start with Facebook Authentication first
Facebook Authentication :
add gem 'omniauth-facebook' to your gem file.
First of all you need to create an application in facebook to get the secret key out .
Next, you need to declare the provider in your (config/initializers/devise.rb) and require it
require "omniauth-facebook" config.omniauth :facebook, "APP_ID", "APP_SECRET"if you have done with all the above , you need to make your model (e.g. app/models/user.rb) omniauthable:
devise :omniauthableBetter restart your server to recognize the changes you have made in Devise Initializer.
Now Devise will create the following url methods.
- user_omniauth_authorize_path(provider)
- user_omniauth_callback_path(provide)
use the below line of code in your view file wherever you want to provide the Facebook link to authorize for the users.
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>
When the user clicks on the above link, they will redirects to the Facebook login page, after entering their credentials it will again redirect the user back to our applications Callback method . Its time to create Callback method for our application.
To implement the callback method, first of all we need to again return back to our config/routes.rb file and need to tell Devise , in which controller we are going to create that callback action.
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
Now we we are going to add a new controller file inside our Rails controller directory "app/controllers/users/omniauth_callbacks_controller.rb" and put the following line code in your omniauth_callbacks_controller.rb file.
We will start with Facebook provider first .You have to care about one thing that the action name should be the same as the provider name.
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController def facebook # You need to implement the method below in your model (e.g. app/models/user.rb) @user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user) if @user.persisted? sign_in_and_redirect @user, :event => :authentication #this will throw if @user 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_user_registration_url end end end
What is happening inside this callback action ?
- When the user enters the credentials in the Facebook sign in form, Facebook actually returns the default information's regarding the particular user , and all that information's retrieved from Facebook by omniauth will be available as a hash at request.env["omniauth.auth"]. try to print this hash for better understanding of what information's are retrieved from Facebook.
- Then we are actually calling a method in user model (we are going to add soon ) with request hash as an argument.If the user logs in for the first time "find_for_facebook_oauth()" function will create a new entry in our user model,when the new user user has been saved properly in our database, it return that user and assigns to an instance variable @user.
- If the User model returns a valid user , we should sign in and redirect that user to our application , that is why we are passing the :event => :authentication to the sign_in_and_redirect method to force all authentication callbacks to be called.
- In case the user is not persisted , we store the omniauth data in the session.Notice we store this data using "devise." as key namespace. This is useful because Devise removes all the data starting with "devise." from the session whenever a user signs in, so we get automatic session clean up. At the end, we redirect the user back to our registration form.
- Suppose you doesn't want to provide an external registration event (i.e you want to remove the Registerable module from your app ) . you can comment the Registerable module in the devise User model, it will automatically removes all the links and routes to the registration page. and after that we can redirect to root_path if the user is not persisted.
Now we are going to implement the find_for_facebook_oauth method in our user model (e.g. app/models/user.rb) :
def self.find_for_facebook_oauth(auth, signed_in_resource=nil) user = User.where(:provider => auth.provider, :uid => auth.uid).first if user return user else registered_user = User.where(:email => auth.info.email).first if registered_user return registered_user else user = User.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
Here you will find some changes compared to the github omniauth documentation.the reason why i have made these changes , you can get it from this stckoverflow question .
The method above simply tries to find an existing user by using the provider , but if the user is already registered in our application using the same email id but different provider, then we need to restrict the user from creating again the same user entry, otherwise if the user tries to register with the same email account but different provider, Devise will throw error and will restrict the user from signing in to our application. so that is why we are checking again whether the use have already signed in our application using any provider and we have created the user entry in our application. suppose if we finds the registered user , we need to return the registered user, else if the user is not at all registered in our application we are actually creates that user with attributes name, provider, email and with a random password.
So that is all you want , we have completed the Facebook authentication using omniauth.
Google Authentication :
This is just the same as Facebook authentication, first of all you need to register your app in Google to get the API key.
You need to update the redirect uri for the API access, if you are using localhost use the following " http://localhost:3000/users/auth/google_oauth2/callback " like shown in the image.
add 'gem 'omniauth-google-oauth2' ' to your Gemfile. then bundle install
require it in 'config/initializers/devise.rb'
require "omniauth-google-oauth2" config.omniauth :google_oauth2, "APP_ID", "APP_SECRET", { access_type: "offline", approval_prompt: "" }
Add the following link to your view file where you want to put the google sign in option.
<%= link_to "Sign in with Google", user_omniauth_authorize_path(:google_oauth2) %>
When the user clicks on the above link, they will redirects to the Google login page, after entering their credentials it will again redirect the user back to our applications Callback method.
Inside "omniauth_callback_controller" add the following function "google_oauth2"
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController def google_oauth2 @user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user) if @user.persisted? flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google" sign_in_and_redirect @user, :event => :authentication else session["devise.google_data"] = request.env["omniauth.auth"] redirect_to new_user_registration_url end end end
Inside the user model. "app/models/user.rb"
def self.find_for_google_oauth2(access_token, signed_in_resource=nil) data = access_token.info user = User.where(:provider => access_token.provider, :uid => access_token.uid ).first if user return user else registered_user = User.where(:email => access_token.info.email).first if registered_user return registered_user else user = User.create(name: data["name"], provider:access_token.provider, email: data["email"], uid: access_token.uid , password: Devise.friendly_token[0,20] ) end end end
Google Authentication is now ready to use.
Twitter Authentication :
Important thing in Twitter authentication is Twitter will not provide you email address for any reason , so for the authentications like we did in Facebook and Google, we cant identify a user by using an email whether the user is already registered (i.e have already created in our application) or not.
What i did here is , i have created a fake email by appending the provider id with the domain string "twitter.com" like "2312234@twitter.com".this will be a unique entry.but this also will not solve our problem, because ones the user have registered in our application for the first time the twitter account , we cant get the email that is for sure , but we are creating one with provider id and domain that is also fine. but we cant identify the user whether he is already registered or not when he/she tries to login for the second time by using Facebook or Google provider.
If you are fine with it you can go forward, i know this is not a good solution. but here we cant leave the column email as blank, we have to enter something in email column when creating a user.
So first of all creates your twitter application here for getting the API key. put the website field as the following "http://127.0.0.1:3000/" if your are using localhost and callback URL also as following "http://127.0.0.1:3000/auth/twitter/callback".
Add the gem gem 'omniauth-twitter' to your Gemfile. and bundle install.
Require it in "config/initializers/devise.rb"
require 'omniauth-twitter' config.omniauth :twitter ,"APP_ID", "APP_SECRET"
Add the following link to your view file for twitter sign in option.
When the user clicks on the above link, they will redirects to the Twitter login page, after entering their credentials it will again redirect the user back to our applications Callback method.
Inside "omniauth_callback_controller" create a function named "twitter" like following,<%= link_to "Sign in with Twitter", user_omniauth_authorize_path(:twitter) %>
When the user clicks on the above link, they will redirects to the Twitter login page, after entering their credentials it will again redirect the user back to our applications Callback method.
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController def twitter auth = env["omniauth.auth"] #Rails.logger.info("auth is **************** #{auth.to_yaml}") @user = User.find_for_twitter_oauth(request.env["omniauth.auth"],current_user) if @user.persisted? flash[:notice] = I18n.t "devise.omniauth_callbacks.success" sign_in_and_redirect @user, :event => :authentication else session["devise.twitter_uid"] = request.env["omniauth.auth"] redirect_to new_user_registration_url end end end
inside your "User" model, add the following method.
def self.find_for_twitter_oauth(auth, signed_in_resource=nil) user = User.where(:provider => auth.provider, :uid => auth.uid).first if user return user else registered_user = User.where(:email => auth.uid + "@twitter.com").first if registered_user return registered_user else user = User.create(name:auth.info.name, provider:auth.provider, uid:auth.uid, email:auth.uid+"@twitter.com", password:Devise.friendly_token[0,20] ) end end end
That is all you need about Facebook,Google & Twitter authentication! , try it yourself , have fun coding.
Great post, I just want to get google working. I followed all the instructions but I get this error:
ReplyDelete/usr/local/rvm/gems/ruby-1.9.3-p374@seekr/gems/devise-2.2.3/lib/devise/rails/routes.rb:434:in `set_omniauth_path_prefix!': Wrong OmniAuth configuration. If you are getting this exception, it means that either: (RuntimeError)
1) You are manually setting OmniAuth.config.path_prefix and it doesn't match the Devise one
2) You are setting :omniauthable in more than one model
3) You changed your Devise routes/OmniAuth setting and haven't restarted your server
Any guesses?
Oh so it only happens when I add :omniauthable to my user model, it will start if I have everything else but that.
ReplyDeleteThank you for your valuable comments ... I hope that your doubts has been cleared...
Deletehi, am following this blog to configure facebook authentication with my app, after completing all configuration and trying to login using facebook am getting this error during callback "uninitialized constant Users" what should i do.. could you please help me in this..??
ReplyDeleteCould you please elaborate the Error message , i think you would have missed this line in your route file.
Deletedevise_for :users , :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
I have included it in the routes.rb
Deletethis is my omniauth controller
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
@user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
if @user.persisted?
sign_in_and_redirect @user, :event => :authentication #this will throw if @user 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_user_registration_url
end
end
end
this is my user.rb model
def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
user = User.where(:provider => auth.provider, :uid => auth.uid).first
if user
return user
else
registered_user = User.where(:email => auth.info.email).first
if registered_user
return registered_user
else
user = User.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
this is in my routes.rb file
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
this is the error i found in logs
ActionController::RoutingError (uninitialized constant Users::OmniauthCallbacksController):
activesupport (3.2.9) lib/active_support/inflector/methods.rb:230:in `block in constantize'
activesupport (3.2.9) lib/active_support/inflector/methods.rb:229:in `each'
activesupport (3.2.9) lib/active_support/inflector/methods.rb:229:in `constantize'
i dono where i made mistake
check your omniauth_callback controller path . It should be inside users folder like follows "app/controllers/users/omniauth_callbacks_controller.rb"
DeleteThis comment has been removed by the author.
DeleteI deploy it here, but they don't work. Any instruction for me.
ReplyDeletehttp://hellorubyonrails.herokuapp.com/
Does you code work for these scenarios ?
ReplyDeleteUser registers normally, then tries to log in via Omniauth
User registers via Omniauth, then tries to log in normally
In this scenario , There will not be any Normal Registration Process.. Every time user has to sign in by using either FB, Twitter or Google account. No other Custom Registration Forms and Sign in Forms.
Deleteaahann.. Thanks for you post.
DeleteWhat I noticed with this solution is, if a user registered with FB, and next time tried to login with google.. the line
registered_user = User.where(:email => access_token.info.email).first
will find a user which is used for facebook authentication(considering user uses same email for google and facebook in real life).
Please note user wanted to authenticate with Google credentials but the user would be allowed to login as he was already registered with FB.
do you think this is correct , any pointers to handle same email scenario ?
How I can implement the multiple user with one app for the twitter?
ReplyDeleteBy adding the provider string directly to the user mode, doesn't that prevent a user from being authenticated by more than one program (I.e. Facebook, Google, and Twitter)?
ReplyDeleteafter google login i have a error: Could not authenticate you from GoogleOauth2 because "Invalid credentials". on my sign in page ? can you please explain
ReplyDeleteI followed your steps , but getting unknown facebook action error ,while am trying to signup with Facebook.
ReplyDeleteHi.. while deploying on heroku.. do we have to make any changes to this ??
ReplyDelete