Google SSO with Devise in Ruby on Rails 7
- Published on
NOTE
This is a repost of my Medium article originally published by the Dev Genius publication here.
This article covers the minimum needed to install Google SSO with Devise. It doesn’t cover other good practices along the way. For example, after you run install Devise, it prompts you to include flash messages in your application layout — I won’t be covering these aspects.
Ensure that you have Rails 7 and the compatible Ruby version installed. Below are my versions running on a MacOS Monterey:
$ rails --version && ruby --version
Rails 7.0.3
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21]
Initial gem installation and configuration
Add the following gems to your Gemfile
:
gem "dotenv-rails"
gem "devise"
gem "omniauth"
gem "omniauth-google-oauth2"
gem "omniauth-rails_csrf_protection"
Install these gems:
$ bundle install
Install Devise:
$ rails g devise:install
In devise.rb
, add the following configuration and pass in your credentials (we will cover obtaining these later on):
config.omniauth :google_oauth2, ENV[‘GOOGLE_OAUTH_CLIENT_ID’], ENV[‘GOOGLE_OAUTH_CLIENT_SECRET’]
Set up Devise’s User model
Create the User model:
$ rails g devise user
Hold off on running a migration. We’ll add some tweaks first.
Add the omniauth
module to user.rb
:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
**# for Google OmniAuth
:omniauthable, omniauth_providers: [:google_oauth2]**
end
Go to the migration file in the db/migrate/
directory. Add additional columns to the users database:
class DeviseCreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t| **## Custom columns**
**t.string :full_name
t.string :uid
t.string :avatar_url
t.string :provider** ## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: "" ...
NOTE
Mine is called 20220709132706_devise_create_users.rb
, but yours will have a slightly different name based on the timestamp of when you created the model.
Migrate the database:
$ rails db:migrate
Set up Devise’s controllers
Create controllers for Devise users:
$ rails g devise:controllers users
Specify these controllers in Devise’s configuration in routes.rb
:
Rails.application.routes.draw do
devise_for :users**, controllers: {
omniauth_callbacks: 'users/omniauth_callbacks',
sessions: 'users/sessions',
registrations: 'users/registrations'**
} ...
Set up methods in the Devise controllers
In the registrations_controller.rb
file, add:
class Users::RegistrationsController < Devise::RegistrationsController **def update_resource(resource, params)
if resource.provider == 'google_oauth2'
params.delete('current_password')
resource.password = params['password']
resource.update_without_password(params)
else
resource.update_with_password(params)
end
end**end
Next, in the sessions_controller.rb
file, add:
class Users::SessionsController < Devise::SessionsController
**def after_sign_out_path_for(_resource_or_scope)
new_user_session_path
end** **def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || root_path
end**
end
Then in the omniauth_callbacks_controller.rb
file, add:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController **def google_oauth2
user = User.from_omniauth(auth)** **if user.present?
sign_out_all_scopes
flash[:success] = t 'devise.omniauth_callbacks.success', kind: 'Google'
sign_in_and_redirect user, event: :authentication
else
flash[:alert] =
t 'devise.omniauth_callbacks.failure', kind: 'Google', reason: "#{auth.info.email} is not authorized."
redirect_to new_user_session_path
end
end** **protected** **def after_omniauth_failure_path_for(_scope)
new_user_session_path
end** **def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || root_path
end** **private** **def auth** [**@**](http://twitter.com/auth)**auth ||= request.env['omniauth.auth']
end**
end
See the from_omniauth
method called in the file above? We’ll define it in the User model inside user.rb
. This method grabs the user data obtained from the authentication request and create a new record in the User model if it doesn’t exist:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
# for Google OmniAuth
:omniauthable, omniauth_providers: [:google_oauth2]
**
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0, 20]
user.full_name = auth.info.name # assuming the user model has a name
user.avatar_url = auth.info.image # assuming the user model has an image
end
end**
end
Obtain the Google Sign In credentials
Go to the Google Cloud Console website, create a new project, then select/open the newly created project.
Navigate to the OAuth consent screen page. Opt for the External
user type. In the next window, enter your app’s name, user support email, and developer contact information. For my hobby projects, I enter my personal email address for both email fields. Click through to the end to finish setting up the OAuth consent screen.
Navigate to the Credentials page. Click on + Create Credentials
and select OAuth client ID
. Select Web application
for the “Application type”. Enter your app’s name. Under “Authorized redirect URIs”, add the following URI:
[http://localhost:3000/users/auth/google_oauth2/callback](http://localhost:3000/users/auth/google_oauth2/callback)
If/when you have deployed an app to production, return to this page and add your app’s domain as another URI with the same format:
[http://**yourwebsite.com**/users/auth/google_oauth2/callback](http://localhost:3000/users/auth/google_oauth2/callback)
Voila. You should see a popup with your Google Client ID and Client Secret.
Create a file called .env
in your app’s root directory and place your Google Client ID and Secret in it:
GOOGLE_OAUTH_CLIENT_ID=Your client id
GOOGLE_OAUTH_CLIENT_SECRET=Your client secret
Add /.env
to your .gitignore
file so that you don’t expose these credentials to bad actors.
Set up the Google Sign In button
Create Devise views:
$ rails g devise:views
Download a Google Sign In button image file into your app/assets/images/
directory. I recommend downloading the files provided on Google’s Sign-In Branding Guidelines page — my favorite is this one as it works in both light and dark modes:
Update the login link in _links.html.erb
to properly configure it with Google OmniAuth and turn it into a clickable button:
...<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
**<%= form_for "Login",
url: omniauth_authorize_path(resource_name, provider),
method: :post,
data: {turbo: "false"} do |f| %>
<%= f.submit "Login", type: "image", src: url_for("/assets/btn_google_signin_dark_normal_web.png") %>
<% end %>**
<% end %>
<% end %>...
Update the image path above (_/assets/btn_google_signin_dark_normal_web.png_
) to match the path of the button image you end up picking.
Finally, place the below code in temporarily on your home page view, or wherever you want to surface these links, to test your newly added Google sign in / sign out features:
<% if current_user %>
<h2><%= current_user.email %></h2>
<%= image_tag(current_user.avatar_url) %>
<%= link_to "Edit Account", edit_user_registration_path %>
<%= button_to "Logout", destroy_user_session_path, data: {turbo: "false"}, method: :delete %>
<% else %>
<%= link_to "Login", new_user_session_path %>
<% end %>