Signup and Login using Google Oauth 2 API in Rails 5.2

Get the client_id and client_secret for your domain from Google Developer Console. Setup the credentials.

EDITOR="mate --wait" bin/rails credentials:edit

Add:

google:
  client_id: your-secret-id
  client_secret: your-client-secret

You can read these in your code:

Rails.application.credentials.google[:client_id]

Encapsulate the credential access in a app/lib/credential.rb class.

class Credential
  def self.google_client_id
    Rails.application.credentials.google[:client_id]
  end

  def self.google_client_secret
    Rails.application.credentials.google[:client_secret]
  end
end

Create privacy.html and save it in public folder of the Rails app. This is needed by Google, since they check it. Add the ominauth-google-oauth2 gem to Gemfile.

gem 'omniauth-google-oauth2'

Run bundle. Dependencies of omniauth-google-oauth2 are jwt, omniauth and omniauth-oauth2 gems. I browsed the repos of these dependencies. The contributors and the code base looks solid. Specify the credentials in config/initializers/omniauth.rb:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :google_oauth2, Credential.google_client_id, Credential.google_client_secret
end

Define the routes for Google authentication:

get 'auth/:provider/callback', to: 'sessions#google_auth'
get 'auth/failure', to: redirect('/')

Create the user model.

rails g model user name email google_token google_refresh_token

Run the migration.

rails db:migrate

Implement the google_auth method in app/controllers/sessions_controller.rb:

def google_auth
  # Get access tokens from the google server
  access_token = request.env["omniauth.auth"]
  user = User.create_from_omniauth(access_token)

  cookies.signed[:user_id] = user.id
  # Access_token is used to authenticate request made from the rails application to the google server
  user.google_token = access_token.credentials.token
  # Refresh_token to request new access_token
  # Note: Refresh_token is only sent once during the first request
  refresh_token = access_token.credentials.refresh_token
  user.google_refresh_token = refresh_token if refresh_token.present?
  user.save

  flash[:success] = 'You are logged in'

  redirect_to root_path
end

Implement the create_from_omniauth method is user model.

class User < ApplicationRecord
  def self.create_from_omniauth(auth)
    # Creates a new user only if it doesn't exist
    where(email: auth.info.email).first_or_initialize do |user|
      user.name = auth.info.name
      user.email = auth.info.email
    end
  end
end

In the credentials page, provide the call back URL as https://your-domain.com/auth/google_oauth2/callback. You will now be able to register and login using Google Oauth2 in your Rails app.

The sessions controller with destroy action used for logout.

class SessionsController < ApplicationController  
  def google_auth
    # Get access tokens from the google server
    access_token = request.env["omniauth.auth"]
    user = User.create_from_omniauth(access_token)

    session[:user_id] = user.id
    # Access_token is used to authenticate request made from the rails application to the google server
    user.google_token = access_token.credentials.token
    # Refresh_token to request new access_token
    # Note: Refresh_token is only sent once during the first request
    refresh_token = access_token.credentials.refresh_token
    user.google_refresh_token = refresh_token if refresh_token.present?
    user.save

    flash[:success] = 'You are logged in'

    redirect_to root_path
  end

  def destroy
    session[:user_id] = nil

    redirect_to root_path
  end
end

The application controller implements the current_user method.

class ApplicationController < ActionController::Base
  helper_method :current_user

  def current_user
    return false if session[:user_id].nil?    
    User.find(session[:user_id]) 
  end
end

The routes.rb:

Rails.application.routes.draw do
  get "/auth/:provider/callback", to: "sessions#google_auth"
  get 'auth/failure', to: redirect('/')
  delete 'signout', to: 'sessions#destroy', as: 'signout'

  root 'home#index'
end

The application layout file:

<!DOCTYPE html>
<html>
  <head>
    <title>Focus</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <a class="navbar-brand" href="/">Focus</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav mr-auto mt-2 mt-lg-0">        
        <% if current_user %>
            <li class="nav-item active">
              <a class="nav-link" href="/bookmarks">Bookmarks <span class="sr-only">(current)</span></a>
            </li>

            <li class="nav-item">
              <%= link_to "Logout", signout_path, method: :delete, class: 'nav-link' %>
            </li>
        <% else %>
            <li class="nav-item">
              <%= link_to 'Signin', '/auth/google_oauth2', class: 'nav-link' %>
            </li>            
        <% end %>

        <li class="nav-item">
          <a class="nav-link" href="/feedback">Feedback</a>
        </li>
      </ul>
      <% if current_user %>
        <form class="form-inline my-2 my-lg-0">
          <input class="form-control mr-sm-2" type="search" placeholder="Search term" id="search-card">
          <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
        </form>
      <% end %>
    </div>
  </nav>

  <body>
    <div class="container">
      <% flash.each do |key, value| %>
        <div class="<%= flash_class(key) %>">
          <%= value %>
        </div>
      <% end %>

      <%= yield %>
    </div>
  </body>
</html>

The application helper that implements the notification styling for Bootstrap 4.

module ApplicationHelper
  def flash_class(level)
    case level
      when 'notice' then "alert alert-info"
      when 'success' then "alert alert-success"
      when 'error' then "alert alert-danger"
      when 'alert' then "alert alert-warning"
    end
  end
end

Google Signin is a gem released by Basecamp that you can use an alternative to the solution in this article.


Related Articles