- Login while maintaining single User even if multiple emails associated with different social logins
- Gemset ruby-2.3.0@rails5.0.0.beta3
- Ruby 2.3.0
- Rails 5.0.0.beta3
- basic app generated by template.rb (github.com/KudosX/template.rb)
- added gems:
gem 'devise'
gem 'therubyracer'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-linkedin'
gem 'omniauth-instagram'
- http://blogs.nbostech.com/2015/08/loginregistration-social-signup-using-ruby-on-rails/
- http://blog.nbostech.com/2015/09/managing-multiple-providerssocial-login-with-existing-user-account-in-rails/
- http://sourcey.com/rails-4-omniauth-using-devise-with-twitter-facebook-and-linkedin/
- https://gist.github.com/blairanderson/761ae067876930523482
- http://davidlesches.com/blog/clean-oauth-for-rails-an-object-oriented-approach
- https://github.com/davidlesches/clean-oauth-core/tree/master/app
- https://github.com/intridea/omniauth/wiki/Managing-Multiple-Providers
- http://www.sitepoint.com/rails-authentication-oauth-2-0-omniauth/
- https://github.com/intridea/omniauth/wiki/List-of-Strategies
- https://github.com/mohitjain/social-login-in-rails/tree/master/app
- https://github.com/arsduo/koala
- https://github.com/sferik/twitter
- https://github.com/arunagw/omniauth-twitter
- https://github.com/jot/omniauth-pinterest/
- https://launchschool.com/blog/facebook-graph-api-using-omniauth-facebook-and-koala
- http://snippets.aktagon.com/snippets/512-how-to-post-a-message-to-the-facebook-wall-with-omniauth-devise
- http://revelry.co/a-beginners-guide-to-using-the-facebook-api-in-your-rails-application-part-1/
- https://github.com/skorks/omniauth-linkedin
brew install v8-315
bundle config --local build.libv8 --with-system-v8
bundle config --local build.therubyracer --with-v8-dir=/usr/local/opt/v8-315
bundle
rails g devise:install
rails g devise User
rake db:create
rake db:migrate
- add to application_controller just before
end
before_action :authenticate_user!, :except => [:index, :about, :contact, :faq]
rake routes
existing routes
- localhost:3000/users/sign_in
- localhost:3000/users/sign_up
rails g devise:views
creates views/devise
- stop server, run
rails g migration AddColumnsToUsers provider uid first_name last_name
- add
:default => nil
to :first_name and :last_name columns in migration - then run
rake db:migrate
- add this code to views/devise/registrations/new.html.erb and edit.html.erb
- add just below
<div class="form-inputs">
<div class="field">
<%= f.label :first_name %><br />
<%= f.text_field :first_name%>
</div>
<div class="field">
<%= f.label :last_name %><br />
<%= f.text_field :last_name %>
</div>
- add logic to controllers/application_controller.rb just after before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name])
end
- https://dev.twitter.com/oauth/overview/application-owner-access-tokens
- https://apps.twitter.com to create your application
- authentication options: https://github.com/arunagw/omniauth-twitter#authentication-options
- below are the URL callbacks for the providers, can't use localhost but, IP address
- Facebook: http://localhost:3000/users/auth/facebook/callback
- Twitter: http://127.0.0.1:3000/users/auth/twitter/callback
- Linkedin: http://localhost:3000/users/auth/linkedin/callback
- Instagram: http://localhost:3000/users/auth/instagram/callback
- for development mode only
nano .bash_profile
will open your bash file- NOTE: close terminal to reset .bash_profile
export TWITTER_KEY="xxxxxkey_from_twitter_apixxxxxx"
export TWITTER_SECRET="xxxxxxsecret_from_twitter_apixxxxxx"
config.omniauth :twitter, ENV["TWITTER_KEY"], ENV["TWITTER_SECRET"],
scope: 'public_profile', info_fields: 'id,name,link'
- add logic to models/user.rb, under class User
:rememberable, :trackable, :validatable,
:omniauthable, :omniauth_providers => [:twitter]
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
end
end
Rails.application.routes.draw do
devise_for :users, :controllers => { :omniauth_callbacks => "callbacks" }
- each provider will need it's own method under CallbacksController class
class CallbacksController < Devise::OmniauthCallbacksController
def twitter
@user = User.from_omniauth(request.env["omniauth.auth"])
sign_in_and_redirect @user
end
end
- create oauth model
rails g model authentication provider:string uid:string user_id:integer
- modify authentication model as follows
class Authentication < ApplicationRecord
belongs_to :user
validates_presence_of :user_id, :uid, :provider
validates_uniqueness_of :uid, :scope => :provider
def provider_name
provider.titleize
end
end
- add to user.rb
has_many :authentications
- remove oauth from table
rails g migration remove_provider_fileds_from_user
- add code to RemoveProvider migration
def change
remove_column :users, :provider
remove_column :users, :uid
end
- run
rake db:migrate
- when user signs in, look for existing authorizations for that external account
- create a user if no authorization is found
- add an authorization to an existing user if user is already logged in
- create controller
rails g controller authentications
- add the following code to authentication_controller.rb to look like this
class AuthenticationsController < ApplicationController
def index
@authentications = current_user.authentications if current_user
end
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, authentication.user)
elsif current_user
current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
else
user = User.new
user.apply_omniauth(omniauth)
if user.save
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omniauth.except('extra')
redirect_to new_user_registration_url
end
end
end
def destroy
@authentication = current_user.authentications.find(params[:id])
@authentication.destroy
flash[:notice] = "Successfully destroyed authentication."
redirect_to authentications_url
end
end
- add methods to the user.rb model
has_many :authentications
def apply_omniauth(omniauth)
authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'])
end
def password_required?
(authentications.empty? || !password.blank?) && super
end
def existing_auth_providers
ps = self.authentications.all
if ps.size > 0
return ps.map(&:provider)
else
return []
end
end
- create views/authentications/index.html.erb that will show number of authentications of user
- add the following code to authentications/index.html.erb
<% "Sign In Options" %>
<% if @authentications %>
<% unless @authentications.empty? %>
<p><strong>You have linked these services with your account:</strong></p>
<div class="authentications">
<% for authentication in @authentications %>
<div class="authentication">
<%= image_tag "#{authentication.provider}_icon.png", size: "32x32"%>
<div class="provider"><%= authentication.provider_name %></div>
<div class="uid"><%= authentication.uid %></div>
<%= link_to "X", authentication, :confirm => 'Are you sure you want to remove this authentication option?', :method => :delete, :class => "remove" %>
</div>
<% end %>
<div class="clear"></div>
</div>
<% end %>
<% else %>
<p><strong>Sign in through one of these services:</strong></p>
<% end %>
<p><strong>Add another service to sign in with:</strong></p>
<%- current_user.class.omniauth_providers.each do |provider| %>
<%- if !current_user.existing_auth_providers.include?(provider.to_s) %>
<%= link_to omniauth_authorize_path(current_user.class, provider) do %>
<%= image_tag "#{provider.to_s}_icon.png", size: "32x32" %>
<% end %>
<% end %>
<% end -%>
<div class="clear"></div>
<% unless user_signed_in? %>
<p>
<strong>Don't use these services?</strong>
<%= link_to "Sign up", new_user_registration_path %> or
<%= link_to "sign in", new_user_session_path %> with a password.
</p>
<% end %>
- create a registration controller and update code
- copy devise registrations views (create, edit) and change code
- tell devise routes to use our registrations controller instead of its own controller
- update the callback controller logic same as authentication controller
- create registrations controller
rails g controller registrations
- add the following code to registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
def create
super
session[:omniauth] = nil unless @user.new_record?
end
private
def build_resource(*args)
super
if session[:omniauth]
@user.apply_omniauth(session[:omniauth])
@user.valid?
end
end
end
- copy views/devise/registrations to views/registrations
- update the code in views/registrations/new.html.erb as follows
<div class="border-form-div">
<h2>Sign up</h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<p><%= f.label :first_name %><br />
<%= f.text_field :first_name%></p>
<p><%= f.label :last_name %><br />
<%= f.text_field :last_name %></p>
<p><%= f.label :email %><br />
<%= f.text_field :email %></p>
<% if @user.password_required? %>
<p><%= f.label :password %><br />
<%= f.password_field :password %></p>
<p><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></p>
<% end %>
<p style="text-align: center;"><%= f.submit "Sign up", :class => 'btn_login' %></p>
<% end %>
<%= render :partial => "devise/shared/links" %>
</div>
- update the code in views/registrations/edit.html.erb as follows
<div class="border-form-div">
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => "edit_user_form"}) do |f| %>
<%= devise_error_messages! %>
<p><%= f.label :first_name %><br />
<%= f.text_field :first_name%></p>
<p><%= f.label :last_name %><br />
<%= f.text_field :last_name %></p>
<p><%= f.label :email %><br />
<%= f.text_field :email %></p>
<p><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
<%= f.password_field :password %></p>
<p><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></p>
<p><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
<%= f.password_field :current_password %></p>
<p style="text-align: center;"><%= f.submit "Update", {:class => "btn_login"} %></p>
<% end %>
<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>
<%= link_to "Back", :back %>
</div>
- add to routes.rb to make it look like
devise_for :users, :controllers => { :registrations => 'registrations', :omniauth_callbacks => "callbacks"}
post '/auth/:provider/callback' => 'authentications#create'
- make callbacks_controller.rb look like the following
class CallbacksController < Devise::OmniauthCallbacksController
def all
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, authentication.user)
elsif current_user
current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
else
user = User.new
user.apply_omniauth(omniauth)
if user.save
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omniauth.except('extra')
redirect_to new_user_registration_url
end
end
end
alias_method :facebook, :all
alias_method :twitter, :all
end
- add to class:
validates :email, presence: true, unless: :twitter?
- add method to user.rb:
def provider
end
def twitter?
self.provider == 'twitter'
end
- refactored user.rb, def self.from_omniauth(auth) method and looks like:
user.password = Devise.friendly_token[0,20]
user.save
user
end
- go to https://developers.facebook.com and add new app
- add the following to devise.rb
config.omniauth :facebook, ENV["FACEBOOK_ID"], ENV["FACEBOOK_SECRET"],
scope: 'public_profile', info_fields: 'id,name,link'
- add Facebook ID and Secret to your .bash_profile as in step 9
- close terminal and open to reset .bash_profile
- scope list: https://developers.facebook.com/docs/facebook-login/permissions/v2.3#reference
- info_fields: https://developers.facebook.com/docs/graph-api/reference/user/
- config settings: https://github.com/mkdynamic/omniauth-facebook#configuring
- make the model/user.rb look like this
:omniauthable, :omniauth_providers => [:twitter, :facebook]
- add the following method to model/user.rb
def facebook?
self.provider == 'facebook'
end
- make sure
alias_method :facebook, :all
is added in callbacks_controller
- go to https://www.linkedin.com/developer/apps and add new app
- add the following to devise.rb
config.omniauth :linkedin, ENV["LINKEDIN_ID"], ENV["LINKEDIN_SECRET"],
scope: 'r_basicprofile', fields: ['id', 'first-name', 'last-name', 'location', 'picture-url', 'public-profile-url']
- add Linkedin ID and Secret to your .bash_profile as in step 9
- close terminal and open to reset .bash_profile
- profile fields: https://github.com/decioferreira/omniauth-linkedin-oauth2#profile-fields
- make the model/user.rb look like this
:omniauthable, :omniauth_providers => [:twitter, :facebook, :linkedin]
- add the following method to model/user.rb
def linkedin?
self.provider == 'linkedin'
end
- make sure
alias_method :linkedin, :all
is added in callbacks_controller
- developer site: https://www.instagram.com/developer/
- add the following to devise.rb
config.omniauth :instagram, ENV["INSTAGRAM_ID"], ENV["INSTAGRAM_SECRET"]
- add Instagram ID and Secret to your .bash_profile as in step 9
- close terminal and open to reset .bash_profile
- Documentation: https://github.com/ropiku/omniauth-instagram
- make the model/user.rb look like this
:omniauthable, :omniauth_providers => [:twitter, :facebook, :linkedin, :instagram]
- add the following method to model/user.rb
def instagram?
self.provider == 'instagram'
end
- make sure
alias_method :instagram, :all
is added in callbacks_controller - instagram has a review policy for app approval and using scopes, must submit video.
-
cloned from: https://github.com/trueinviso/multiple-omniauth
-
based on this thread:
-
repo was done in response to above issue by trueinviso and repo is below
-
http://sourcey.com/rails-4-omniauth-using-devise-with-twitter-facebook-and-linkedin/
-
I nuked gemlock file, ran bundle, and migrated database