I have just finished the Michael Hartl tutorial. I am trying to implement omniauth-facebook to sign-up and sign-in users. I am having trouble creating the master variable to use in the create
action in the sessions controller. My assumption is that I should put in an if else
statement to see if the master is either signing on through facebook or through the default form? Or should I use and or
statement:
(master = Master.find_by(email: params[:session][:email].downcase) || Master.from_omniauth(env["omniauth.auth"])) ?
Here is the omniauth-facebook sessons controller create action code:
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url
end
And here is my current sessions controller create action code:
class SessionsController < ApplicationController
def new
end
def create
master = Master.find_by(email: params[:session][:email].downcase)
if master && master.authenticate(params[:session][:password])
sign_in master
redirect_back_or master
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
.
.
.
end
Whilst I might be late by one year to answer this question, I still think that new viewers with the same sort of problem like you had a year ago might benefit from the following answer
As I understand it the actual question is:
How does one integrate Facebook Authentication with an Authentication system that is built from Scratch?
Since you said you used Michael Hartl's tutorial I went ahead and adapted my answer to an authentication system written by Michael himself. Now let's go over each piece of code one step at a time.
First of all we need to create two additional columns to our existing Users table for the provider and uid as follows...
rails g migration add_facebook_auth_to_users provider uid
...and add omniauth-facebook gem to the Gemfile...
Gemfile
gem 'omniauth-facebook', '2.0.1'
Don't forget to run bundle install
and migrate your database after.
Next we would need to go ahead and edit our Sessions controller...
sessions_controller.rb
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
if user.activated?
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
message = "Account not activated. "
message += "Check your email for the activation link."
flash[:warning] = message
redirect_to root_url
end
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def create_facebook
user = User.from_omniauth(env["omniauth.auth"])
log_in user
redirect_back_or user
end
Sessions controller above contains two create actions. First one is the untouched method taken straight from the book, when the second one is a piece of code I used specifically for Facebook authentications.
Line user = User.from_omniauth(env["omniauth.auth"])
has two purposes. Create a user IF his/her unique facebook uid is not in the database ELSE log this person in the app if the uid exists. More on this later.
Next lets create a working .from_omniauth method that you briefly showed inside of your code in your question...
user.rb
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name unless user.name != nil
user.email = SecureRandom.hex + '@example.com' unless user.email != nil
user.activated = true
user.password = SecureRandom.urlsafe_base64 unless user.password != nil
user.save!
end
end
Here we try to find a user that matches the given provider
and uid
values from the hash and then we call first_or_initialize
on the result of this. This method will either return the first matching record or initialize a new record with the parameters that were passed in. We then call tap
on this to pass that User instance to the block. Inside the block we set various attributes on the user based on values from the OmniAuth hash.
You might be interested as to why did I put the unless
statement after some of the attribute initializations. Well consider the situation whereby the user wants to update the profile, like change the name or smth, and then logs out. When that user finally decides to log back in, the .from_omniauth
method will overwrite the User's in question update to the original facebook values, unless we stop it from doing so.
Also notice the use of SecureRandom
library. Inside of the traditional authentication that was used by Michael Hartl inside his book, he introduces validations to email and password submissions. Emails must be neither blank nor taken. Likewise, a password has to be greater than 6 characters in length. Since emails have to be present and unique I decided to create dummy emails using SecureRandom.hex + @example.com
. This will create a random hexadecimal string, like 52750b30ffbc7de3b362
, and append it to @example.com
, hence generating the unique dummy email. Same goes for the password, however, I preferred to generate a random base64 string using SecureRandom.urlsafe_base64
. The most important thing to remember that Facebook users don't need to know this information to login since that is the whole point of using Facebook authentication. This allows them to add this information with real data later on if they desire to do so.
Now would be a good time to add the button on the main application page for users to let them actually log in into the app...
_header.html.erb
<% if logged_in? %>
.
.
.
<% else %>
.
.
<li><%= link_to "Sign in with Facebook", "/auth/facebook", id: "sign_in" %></li>
<% end %>
So, now we can send a request to facebook. One problem remains that we cannot actually handle the callback. To do so we need to do the following...
Add get 'auth/:provider/callback' => 'sessions#create_facebook'
to the routes.rb file
Use convenient Javascript SDK provided by Facebook. Let's create a custom javascript file that uses this SDK...
app/assets/javascripts/facebook.js.erb
jQuery(function() {
$('body').prepend('<div id="fb-root"></div>');
return $.ajax({
url: window.location.protocol + "//connect.facebook.net/en_US/sdk.js",
dataType: 'script',
cache: true
});
});
window.fbAsyncInit = function() {
FB.init({
appId: 'your_facebook_app_id_goes_here',
version: 'v2.3',
cookie: true
});
$('#sign_in').click(function(e) {
e.preventDefault();
return FB.login(function(response) {
if (response.authResponse) {
return window.location = '/auth/facebook/callback';
}
});
});
return $('#sign_out').click(function(e) {
FB.getLoginStatus(function(response) {
if (response.authResponse) {
return FB.logout();
}
});
return true;
});
};
And that is it, except for one small caveat...
If user decides to go and edit the profile, he/she would see our dummy email displayed right in the middle.This happends because Rails is smart enough to autofill User's information.In this case this, frankly, is quite embarassing. Good thing, however, that it is an easy fix. Just set the value to nil inside the form, like so...
<%= f.email_field :email, value: nil, class: 'form-control' %>
Hopefully this helps people who would like to go the route of building authentication systems from scratch :)
Ok I was trying to integrate omniauth with the custom authorization code from the tutorial. After some research I decided to remove all the tutorial authorization code and go with Devise instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With