Social Media Authentication On Rails- Part 2

Posted on the 13 October 2014 by Abhishek Somani @somaniabhi
In the last post , we have discussed how to get api keys and access for multiple social media platform .In this post we will to setup rails to integrate these applications .Code SetUp:Now its Time to Set Up Rails Application with MongoDB , Rails & Omniauth Gem(s).we will be using “ruby 2.1.1p76” with “Rails 4.1.0” for our application.Step 1:In Gem file add the following Gems:

gem 'rails', '4.1.0'
gem 'mongoid', git: 'https://github.com/mongoid/mongoid.git'
gem 'devise'
gem 'bson_ext'
gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-linkedin'
gem 'omniauth-twitter'
gem "omniauth-google-oauth2"
gem 'twitter'
gem "linkedin"
gem 'oauth2'
Step 2:Follow the link and install devise gem to your application.For Omniauth with devise follow the link which will be usefull for development and debugging your application. Step 3:Include required Gems in your initializers/devise.rb file and add your API KEYS with API Secret.

require 'devise/orm/mongoid'
require 'omniauth-twitter'
require 'omniauth-google-oauth2'
require 'omniauth-linkedin'
require 'omniauth-facebook'
require 'omniauth-github'
Configuring API Keys:

config.omniauth :twitter, "API KEY", "API SECRET"
config.omniauth :linkedin, "API KEY", "API SECRET", :scope => 'r_basicprofile r_emailaddress rw_nus'
config.omniauth :facebook, 'API KEY', 'API SECRET'
config.omniauth :google_oauth2, 'API KEY', 'API SECRET'
Add the following links to devise registrations/new or shared/_links.html.erb Open Up Your User Model and Configure it as described below , fields for mongo db can be configured as per your requirement :

class User
include Mongoid::Document
devise :omniauthable, :omniauth_providers => [:facebook,:twitter,:linkedin,:google_oauth2]
embeds_one :user_linkedin_connection, :class_name => 'User::LinkedinConnection'
embeds_one :user_twitter_connection, :class_name => 'User::TwitterConnection'
# Configured for Getting mongo key from Session in rails 4 (Mongoid)
class << self
def serialize_from_session(key, salt)
record = to_adapter.get(key.first["$oid"]) if key.present? # to_adapter.get(key.to_s)
record if record & record.authenticatable_salt == salt
end
end
def self.connect_to_linkedin(auth)
self.provider = auth.provider
self.uid = auth.uid
self.user_linkedin_connection = User::LinkedinConnection.new(:token => auth["extra"]["access_token"].token, :secret => auth["extra"]["access_token"].secret)
unless self.save
return false
end
true
end
def self.disconnect_from_linkedin!
self.provider = nil
self.uid = nil
self.user_linkedin_connection = nil
self.save!
end
def self.find_for_linkedin_oauth(auth, signed_in_resource=nil)
@user = User.where(:provider => auth.provider, :uid => auth.uid).first
if @user
@user.connect_to_linkedin(request.env["omniauth.auth"])
sign_in_and_redirect @user, :event => :authentication
set_flash_message(:notice, :success, :kind => "LinkedIn") if is_navigational_format?
else
flash[:notice] = "Couldn't find a user connected to your LinkedIn account. Please sign in and then connect your account to LinkedIn."
redirect_to new_user_session_url
end
end
def self.find_for_facebook_oauth(auth)
where(auth.slice(:provider, :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]
user.name = auth.info.name # assuming the user model has a name
end
end
def self.find_for_google_oauth2(access_token, signed_in_resource=nil)
data = access_token.info
user = User.where(:email => data["email"],:provider => "Google").first
unless user
user = User.create( name: data["name"],email: data["email"],provider: "Google", password: Devise.friendly_token[0,20] )
end
user
end
def self.find_for_github_oauth(auth)
record = where(provider: auth.provider, uid: auth.uid.to_s).first
record || create(provider: auth.provider, uid: auth.uid, email: auth.info.email, password: Devise.friendly_token[0,20], name: auth.info.name )
end
end
Create two More files named linkedin_connection.rb and twitter_connection.rb inside app/models/user/ which will contain the token and secret provided from provider. linkedin_connection.rb:

class User::LinkedinConnection
include Mongoid::Document
include Mongoid::Timestamps
embedded_in :user
field :token
field :secret
end
twitter_connection.rb:

class User::TwitterConnection
include Mongoid::Document field :token
include Mongoid::Timestamps field :secret
embedded_in :user
end
Once we get a Success request from Provider , response moves on to omniauth call_back controller where we need to store the response and maintain the session.Create a omniauth_callback controller and paste the code as provided below:

class OmniauthCallbacksController < Devise::OmniauthCallbacksController
before_filter :authenticate_user!
# Linkedin authentication
def linkedin
@user = User.where(:provider => request.env["omniauth.auth"].provider, :uid => request.env["omniauth.auth"].uid).first
if @user.present?
sign_in_and_redirect @user, :event => :authentication
set_flash_message(:notice, :success, :kind => "LinkedIn") if is_navigational_format?
else
auth = request.env["omniauth.auth"]
@user = User.new #create new user to save in the database
@user.email= auth.info.email #save user email
@user.provider = auth.provider
@user.uid = auth.uid
@user.user_linkedin_connection = User::LinkedinConnection.new(:token => auth["extra"]["access_token"].token, :secret => auth["extra"]["access_token"].secret)
@user.save(:validate => false) #password for the linkdin to be stored
sign_in_and_redirect @user, :event => :authentication
set_flash_message(:notice, :success, :kind => "LinkedIn") if is_navigational_format?
end
end
# Facebook authentication
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"])
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
# Twitter authentication
def twitter
@user = User.where(:provider => request.env["omniauth.auth"].provider, :uid => request.env["omniauth.auth"].uid).first
if @user.present?
sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format?
else
auth = request.env["omniauth.auth"] # contains all the information about the user login profile.
@user = User.create(name: auth.extra.raw_info.name,
provider: auth.provider,
uid: auth.uid,
email: auth.uid+"@twitter.com", #create user email
password: Devise.friendly_token[0,20],
user_twitter_connection: User::TwitterConnection.new(:token => auth.credentials.token, :secret => auth.credentials.secret )
)
sign_in_and_redirect @user, :event => :authentication
set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format?
end
end
# Google authentication
def google_oauth2
# You need to implement the method below in your model (e.g. app/models/user.rb)
@user = User.find_for_google_oauth2(request.env["omniauth.auth"])
if @user.persisted?
sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
set_flash_message(:notice, :success, :kind => "Google") if is_navigational_format?
# flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
# sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
Now we are Good to Go ahead and start authenticating with social websites in our Rails app.Note: • Application status in Provider Settings should be live• Call back URL should match your application routes• It is Good to go with Storing session in DB as sometimes cookie_store fails when a logged in user fetches request from provider. Thanks to Santosh for wroting this post .