Social Login

As you might have seen normal signup and login is rather hectic.I mean you have to feel the form, rememember your password.The database maybe compromised and password even though hashed can be reverse engineered and this poses a security threat.

We can us social media,such as facebook, twitter and google to login.Our case we will use google we aid of Omniauth.For more on visit Omniauth

setting up

Create an app with google

Since will be using google apis for authentication purpose we need to register an application with them at google cloud platform(GCP). Visit GCP

It may take a few minute to create, the

Search for Google+API and enable it Search for Contacts API The sidebar on the left should have a Credentials button, click it, and hit Create credentials Before you create the credentials, you will be prompted to create the consent screen. The only mandatory field is the app name, so go ahead and fill that in. Now, you will be able to create your credentials. The application type is Web application. You can name something descriptive. The Authorized redirect URIs should have one URL of the form:

http://localhost:3000/auth/google_oauth2/callback

The OAuth2.0 protocol which OmniAuth abstracts, relies on a callback URL to pass the authenticated user object into your application. OmniAuth handles the necessary juggling to get that user object in the first place. The developer should only be responsible for handling that user object, which we will do in a bit. After filling out this form, you will be presented with your Client ID and Client Secret, both of which are used for authorizing your application to use Google’s APIs.

Intergrating with rails app

We will add gem omniauth-google-oauth2, which is the Google strategy for OmniAuth.

Add it in the Gemfile like:

1
gem 'omniauth-google-oauth2'

Configure OmniAuth

Create config/initializers/omniauth.rb.As the name suggests, all code inside of initializers is run when the application starts up. This code adds OmniAuth to the Rack middleware. The provider method also accepts an options hash described here.

Add this to config/initializers/omniauth.rb file.

config/initializers/omniauth.rb

1
2
3
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :google_oauth2, "GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"
end

Replace GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET with the values generated in the Cloud Console.

Creating user

The auth hash returned contains a ton of information about the User.We will choose provider, uid, email, first_name, last_name.

Let us generate user like so:

rails g model User provider uid email first_name last_name

Migrate like so:

rails db:migrate

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class User < ApplicationRecord
    def self.find_or_create_from_auth_hash(auth)
        where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
            user.provider = auth.provider
            user.uid = auth.uid
            user.first_name = auth.info.first_name
            user.last_name = auth.info.last_name
            user.email = auth.info.email        
            user.save!
        end
    end
end

The class method find_or_create_from_auth_hash will use the hash sent by Google to look up our user model in the database. The function first_or_initialize.tap returns the User object if it finds one, updates the user if any of the information has changed, or creates and saves a new one if the user didn’t exist at all. We can identify a user by the provider and the uid . The uid really should be enough to identify the user, but we can avoid the rare collision of more than one provider having the same uid.

Now we are done creating the model, so let’s configure the routes.

Configuring Routes

The routes provide a nice summary of how everything works together. Add the following code to your routes.rb.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Rails.application.routes.draw do

  get 'users/login', to: redirect('/auth/google_oauth2'), as: 'login'
  get 'user/logout', to: 'sessions#destroy', as: 'logout'
  get 'auth/:provider/callback', to: 'sessions#create'
  get 'auth/failure', to: redirect('/')
  get 'home', to: 'home#index'
  get '/users/show', to: 'users#show', as: 'user'

  root to: "home#show"

end

auth/:provider/callback creates a new session with the User object (turned auth hash) returned by Google. It accepts a parameter :provider in case you had multiple providers. If you decide to include multiple providers, then login must redirect to a generic provider selection menu, rather than straight to Google. In our case, auth/google_oauth2/callback would have also worked fine. auth/failure is requested by the provider if the user fails to accept the requested permissions. In our case, we redirect to root if this occurs.

Generating Controller

rails g Home show

rails g Users show

app/controller/home_controller.rb

1
2
3
4
class HomeController < ApplicationController
  def show
  end
end

app/controller/users_controller.rb

1
2
3
4
5
class UsersController < ApplicationController
    before_action :authenticate
    def show
    end
end

The line before_action tells Rails to run the :authenticate method before any action is called. We will define :authenticate soon.

app/controller/sessions_controller.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class SessionsController < ApplicationController

  def create
    @user = User.find_or_create_from_auth_hash(request.env["omniauth.auth"])
    session[:user_id] = @user.id
    redirect_to :user
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_path
  end

end

The create action is called upon the request auth/:provider/callback. That means that create receives the auth hash from the provider, or Google in our case. As you can see, we are calling the find_or_create_from_auth_hash class method we wrote earlier. The hash itself is stored in request.env["omniauth.auth"]. We also put the User ID into the globally accessible session hash. This allows a user to make multiple requests to the app without having to log in. Finally, after the user is logged in, we redirect them to the :me route to view their account specific information. destroy removes the :user_id from the session and redirects the user to root. The next authenticated request the user makes will require a login.

We also need to create the authenticate method in app/controllers/application_controller.rb . Since all other controllers are subclasses of ApplicationController, public methods and variables you define here are inherited by them.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  helper_method :current_user

  def authenticate
    redirect_to :login unless user_signed_in?
  end

  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end

  def user_signed_in?  
    !@current_user.present?
  end

end

Creating view

views/home/show.html.erb

1
2
<h1> Welcome, please login to continue </h1>
<a href="/auth/google_oauth2">Sign in with Google</a>

views/users/show.html.erb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<p>
  <strong>First Name:</strong>
  <%= @currenr_user.first_name %>
</p>

<p>
  <strong>Last Name:</strong>
  <%= @current_user.last_name %>
</p>

<p>
  <strong>Email:</strong>
  <%= @current_user.email %>
</p>
<p> 

Protecting the environmental variable

Your google GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET and secrets and should not be exposed to the version control

We can create a config/development_secrets.yml this file should be add to .gitignore file to keep it our version.

The content of config/development_secrets.yml should be like so:

config/development_secrets.yml

1
2
3
development:  
  GOOGLE_CLIENT_ID: xxxxxxx
  GOOGLE_SECRET_KEY: xxxxxxx

For now we are in development environment.And we setup our environment variables likes replace xxxxx with appropriate values.

Then we need to tweak our omniauth.rb file to our environmental variables like so:

config/initializers/omniauth.rb

1
2
3
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :google_oauth2, Rails.application.secrets.GOOGLE_CLIENT_ID, Rails.application.secrets.GOOGLE_SECRET_KEY
end

Then ignore our secrets likes so:

gitignore

1
config/development_secrets.yml