Ruby on Rails / OAuth2

I tried doing this manually using the oauth2 gem, but while it kind-of works, getting any sort of user information requires you to decode a JWT payload (ugh, OpenID why does Google no longer support you).

Instead I got it working with just the omniauth-google-oauth2 gem. Based on this tutorial which is for an older version of Rails:

  1. Add new bundles and run bundle install:
gem 'haml'
gem 'dotenv-rails'
gem 'figaro'
gem 'omniauth-google-oauth2'
gem 'activerecord-session_store'         # necessary for Rails 4+
  1. Create a new controller for sessions, and a new database model for users:
rails generate session_migration         # or active_record:session_migration for Rails 4+
rails generate controller sessions
rails generate model User provider:string uid:string name:string refresh_token:string access_token:string expires:timestamp
rake db:migrate
  1. Configure the session to use a database rather than cookies which have a 4KB limit:
# config/initializers/session_store.rb

Example::Application.config.session_store :active_record_store
  1. Create your SessionsController:
# /app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def create
    auth = request.env["omniauth.auth"]
    user = User.where(:provider => auth["provider"], :uid => auth["uid"]).first_or_initialize(
      :refresh_token => auth["credentials"]["refresh_token"],
      :access_token => auth["credentials"]["token"],
      :expires => auth["credentials"]["expires_at"],
      :name => auth["info"]["name"],
    )
    url = session[:return_to] || root_path
    session[:return_to] = nil
    url = root_path if url.eql?('/logout')

    if user.save
      session[:user_id] = user.id
      notice = "Signed in!"
      logger.debug "URL to redirect to: #{url}"
      redirect_to url, :notice => notice
    else
      raise "Failed to login"
    end
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_url, :notice => "Signed out!"
  end
end
  1. Login to your Google Developers Console, create a new Project, and visit APIs & Auth:
  1. Edit config/application.yml with these Google keys (figaro will make all of these settings available through ENV[property]):
# config/secrets.yml

# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
  OAUTH_CLIENT_ID: <%= ENV["OAUTH_CLIENT_ID"] %>
  OAUTH_CLIENT_SECRET: <%= ENV["OAUTH_CLIENT_SECRET"] %>
  APPLICATION_CONFIG_SECRET_TOKEN: <%= ENV["APPLICATION_CONFIG_SECRET_TOKEN"] %>

And put your secrets into /.env:

OAUTH_CLIENT_ID: "xyz"
OAUTH_CLIENT_SECRET: "xyz"
APPLICATION_CONFIG_SECRET_TOKEN: "<A LONG SECRET>"
  1. Enable Omniauth to use Googleauth2 as a provider (approval_prompt must be an empty string otherwise it will force a prompt on every login):
# /config/initializers/omniauth.rb

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :google_oauth2,
    ENV['OAUTH_CLIENT_ID'],
    ENV['OAUTH_CLIENT_SECRET'],
    {name: "google_login", approval_prompt: ''}
end
  1. Enable new routes:
# /config/routes.rb

Example::Application.routes.draw do
  # ...
  get "/auth/google_login/callback" => "sessions#create"
  get "/signout" => "sessions#destroy", :as => :signout
end
  1. Now finally, you can add login/logout links in your navigation template, or whatever:
# /app/views/home/index.html.haml

%p
  - if current_user
    = link_to "Sign out", signout_path
    = current_user.name
    = current_user.inspect
  - else
    = link_to "Sign in with Google", "/auth/google_login"
  1. Which uses a helper method, current_user, which loads the User model based on the session user_id:
# /app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  // ...
  helper_method :current_user

  private

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