Oauth Facebook Login
In addition to utilizing the Devise to manage user account credentials by itself, it is also highly common to integrate other user authentication systems directly, such as Google, Facebook, Yahoo, GitHub, etc. The majority of people already have accounts with these popular websites and do not need to register all over again.
In this article, we will integrate Facebook’s third-party authentication.
Table Of Contents
What is Omniauth?
OmniAuth is a library that standardizes multi-provider authentication for web applications. It was created to be powerful, flexible, and do as little as possible. Any developer can create strategies for OmniAuth that can authenticate users via different systems.
To utilize OmniAuth in our app, we must employ one or more strategies. These strategies are often provided as RubyGems. For a full list of these providers, please check OmniAuth’s list of strategies.
What we will integrate here in our application as our third-party authentication is Facebook.
How to install omniauth-facebook
Reminder:
Make sure that your containers are up and running.
In your gemfile, add gem 'omniauth-facebook'
and gem 'omniauth-rails_csrf_protection'
(for OmniAuth 2.0+ versions).
gem 'omniauth-facebook'
gem 'omniauth-rails_csrf_protection'
Then run bundle install.
root@0122:/usr/src/app# bundle install
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies...
...
Fetching omniauth 2.1.1
Installing snaky_hash 2.0.1
Installing omniauth 2.1.1
Fetching oauth2 2.0.9
Fetching omniauth-rails_csrf_protection 1.0.1
Installing oauth2 2.0.9
Fetching omniauth-oauth2 1.8.0
Installing omniauth-rails_csrf_protection 1.0.1
Installing omniauth-oauth2 1.8.0
Fetching omniauth-facebook 9.0.0
Installing omniauth-facebook 9.0.0
Bundle complete! 30 Gemfile dependencies, 117 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
Usage
Lets add the columns provider (string)
, uid (string)
, and raw_data (text)
to User model. These columns will handle the facebook authentication information. Additionally, we will also get the users facebook image, for this we need an avatar (string)
and remote_avatar_url (string)
columns.
root@0122:/usr/src/app# rails g migration AddOmniauthToUsers
invoke active_record
create db/migrate/xxxxxxxxxxxxxx_add_omniauth_to_users.rb
# db/migrate/xxxxxxxxxxxxxx_add_omniauth_to_users.rb
class AddOmniauthToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :uid, :string, null: false, default: ''
add_column :users, :provider, :string, null: false, default: ''
add_column :users, :raw_data, :text
# for user avatar
add_column :users, :avatar, :string
add_column :users, :remote_avatar_url, :string
end
end
Then migrate.
root@0122:/usr/src/app# rails db:migrate
== xxxxxxxxxxxxxx AddOmniauthToUsers: migrating ===============================
-- add_column(:users, :uid, :string, {:null=>false, :default=>""})
-> 0.0088s
-- add_column(:users, :provider, :string, {:null=>false, :default=>""})
-> 0.0086s
-- add_column(:users, :raw_data, :text)
-> 0.0089s
-- add_column(:users, :avatar, :string)
-> 0.0100s
-- add_column(:users, :remote_avatar_url, :string)
-> 0.0083s
== xxxxxxxxxxxxxx AddOmniauthToUsers: migrated (0.0266s) ======================
Declare the provider in config/initializers/devise.rb
.
# config/initializers/devise.rb
# ...
Devise.setup do |config|
# ...
# ==> OmniAuth
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
+ config.omniauth :facebook, 'APP_ID', 'APP_SECRET'
# ...
end
Creating Developer Facebook App.
- Go to Facebook Developer and login your account.
- Navigate to My Apps tab.
- Click
Create App
. - In Select an App Type, choose
Nothing
then clickNext
. - Fill out
App name
andApp contact email
then clickCreate
. (Enter password for confirmation) - Go to
Settings > Basic
, Copy yourAPP_ID
andAPP_SECRET
.
Create a facebook yaml files to store these credentials.
# config/facebook.example.yml
development:
secret: xxxx
app_id: xxxx
test:
secret: xxxx
app_id: xxxx
# config/facebook.yml
development:
secret: xxxx
app_id: xxxx
test:
secret: xxxx
app_id: xxxx
Dont forget to exclude facebook.yml
to your commits.
# .gitignore
# ...
+ /config/facebook.yml
Input the app credentials to devise omniauth config.
# config/initializers/devise.rb
# ...
Devise.setup do |config|
# ...
- config.omniauth :facebook, 'APP_ID', 'APP_SECRET'
+ config.omniauth :facebook, Rails.application.config_for(:facebook)[:app_id],
+ Rails.application.config_for(:facebook)[:secret]
# ...
end
After configuring the strategy, we need to make User model omniauthable.
# app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
- :recoverable, :rememberable, :validatable
+ :recoverable, :rememberable, :validatable,
+ :omniauthable, omniauth_providers: %i[facebook]
# ...
end
By declaring devise as omniauthable, this will create routes for provider we specified. Let’s check the routes that it made.
root@0122:/usr/src/app# rails routes
Prefix Verb URI Pattern Controller#Action
new_user_session GET /users/sign_in(.:format) devise/sessions#new
user_session POST /users/sign_in(.:format) devise/sessions#create
destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
user_facebook_omniauth_authorize GET|POST /users/auth/facebook(.:format) devise/omniauth_callbacks#passthru
user_facebook_omniauth_callback GET|POST /users/auth/facebook/callback(.:format) devise/omniauth_callbacks#facebook
Now add a link for facebook login.
<!-- app/views/layouts/application.html.erb -->
<!-- ... -->
<% else %>
<%= link_to 'Sign in', new_user_session_path %>
+ <%= button_to 'Sign in with Facebook', user_facebook_omniauth_authorize_path,
+ method: :post, data: { turbo: false } %>
<% end %>
<%= link_to "EN", params.permit!.merge(locale: 'en') %>
<%= link_to "zh-CN", params.permit!.merge(locale: 'zh-CN') %>
<%= yield %>
</body>
</html>
The user will be routed to Facebook by clicking on the above link. They will be forwarded to your application’s callback function after entering their credentials.
To implement the callback, to go to our config/routes.rb
file and tell Devise in which controller we will add Omniauth callbacks.
# config/routes.rb
Rails.application.routes.draw do
- devise_for :users
+ devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
# ...
end
Now let’s create a controller Users::OmniauthCallbacksController
, and modify its content.
# app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# See https://github.com/omniauth/omniauth/wiki/FAQ#rails-session-is-clobbered-after-callback-on-developer-strategy
skip_before_action :verify_authenticity_token, only: :facebook
def facebook
# You need to implement the method below in your model (e.g. app/models/user.rb)
@user = User.create_from_provider_data(request.env['omniauth.auth'])
if @user.persisted?
sign_in_and_redirect @user
set_flash_message(:notice, :success, kind: 'Facebook') if is_navigational_format?
else
set_flash_message(:alert, :failure, { kind: 'Facebook', reason: @user.errors&.full_messages&.join('') })
redirect_to new_user_registration_url
end
end
def after_sign_in_path_for(resource)
welcome_path
end
def failure
redirect_to root_path
end
end
- OmniAuth returns all information obtained from Facebook as a hash at
request.env['omniauth.auth']
. - Once a legitimate user has been identified, they may be signed in using one of two Devise methods:
sign_in
orsign_in_and_redirect
. - It is also possible to set a flash message using one of Devise’s predefined messages, although this is entirely up to you.
After we’ve established the controller, we’ll need to define the create_from_provider_data
function in our user model. Every social platform have unique id for its user. We will make use of this upon user creation.
# app/models/user.rb
class User < ApplicationRecord
# ...
def self.create_from_provider_data(provider_data)
find_or_create_by(provider: provider_data.provider, uid: provider_data.uid) do |user|
user.email = provider_data.info.email
user.password = Devise.friendly_token[0, 20]
user.raw_data = provider_data
user.genre = :client
end
end
end
Now that we are done with the callback, we need to declare on our facebook app the domain and redirect url. Given that the facebook does not allow unsafe url, we need to use ngrok
and modify our applications url.
$~> ngrok http 3000
Session Status online
Account xxxxxx
Update update available (version 3.1.1, Ctrl-U to update)
Version 3.0.3
Region Asia Pacific (ap)
Latency 29.425332ms
Web Interface http://127.0.0.1:4040
Forwarding https://xxxx-xxx-xx-xxx-xxx.ap.ngrok.io -> http://localhost:3000
# config/environments/development.rb
require "active_support/core_ext/integer/time"
Rails.application.configure do
# ...
+ config.hosts << 'xxxx-xxx-xx-xxx-xxx.ap.ngrok.io'
end
Now add redirect url to our facebook app:
- Go to Facebook App, then on sidebar you will see
Products
, click theAdd Product
button beside. - Add
Facebook Login
by clickingSet Up
under it. - Ignore the Quickstart and navigate to
Facebook Login > Settings
- Add Valid OAuth Redirect URIs:
https://xxxx-xxx-xx-xxx-xxx.ap.ngrok.io/users/auth/facebook/callback
- To make sure that your uri is valid, you can check it with
Redirect URI Validator
after saving the changes.
The next step is to save the Facebook profile picture. The return data request.env['omniauth.auth']
also contains facebook image url. Carrierwave offers a method where you simply need to provide the image url in the remote url
field and it will take care of the storing/saving of image for you.
Mount ImageUploader
to avatar
column on User model. Then save the facebook image url to remote_avatar_url
.
# app/models/user.rb
class User < ApplicationRecord
# ...
enum genre: { client: 0, admin: 1 }
+ mount_uploader :avatar, ImageUploader
# ...
def self.create_from_provider_data(provider_data)
find_or_create_by(provider: provider_data.provider, uid: provider_data.uid) do |user|
user.email = provider_data.info.email
user.password = Devise.friendly_token[0, 20]
user.raw_data = provider_data
user.genre = :client
+ user.remote_avatar_url = provider_data.info.image
end
end
end
Then display it to welcome page.
<!-- app/views/welcome/index.html.erb -->
<!-- ... -->
- <% if user_signed_in? %>
- <h2> Hello <%= current_user.email %> </h2>
- <%= link_to 'Sign out', destroy_user_session_path, data: { 'turbo-method': :delete } %>
- <% else %>
- <%= link_to 'Sign in', new_user_session_path %>
- <% end %>
+ <br>
+ <%= image_tag current_user.avatar.url, width: 300 if current_user&.avatar&.present? %>
When you finished the setup, you may now try logging in using facebook on our application.
If you wish to login using an email other than the one used to create your Facebook App during development, you must complete one (only one) of the following steps:
- Turn on Live Development then afterwards
Get Advanced Access
withpublic_profile
(underApp Review > Permissions and Features
). - Or add the Facebook Account you will use as Developer (under
App Roles > Roles
). - Or create a Test User (under
App Roles > Test Users
).
We are now able to login using Facebook. That’s all for Oauth Facebook Login.