We will be working from the source code found here.
# Clone and change into the project
$ git clone https://github.com/okeeffed/demo-rails-7-with-devise-series
$ cd demo-rails-7-with-devise-series
# Continue from part 4 - warning that Redis required
$ git checkout 4-seperate-frontends
# Add required Gems
$ bundler add dotenv-rails --group "development,test"
$ bundler add omniauth-github
$ bundler add omniauth-rails_csrf_protection
Setup for GitHub app
It is a prerequisite that you have a GitHub app setup for this. There are plenty of posts/guides out there on doing this, so I will be sharing too much detail.
After you have created it, make sure to copy down the Client ID and Client secret. We will need to add that to our Rails app environment.
Afterwards, around line 274 of the config/initializers/devise.rb file, we can uncomment the line config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' and update it to config.omniauth :github, ENV.fetch('GITHUB_APP_ID'), ENV.fetch('GITHUB_APP_SECRET'), scope: 'user:email'.
# ... omitted
Devise.setup do |config|
# ... rest omitted
# ==> OmniAuth
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
config.omniauth :github, ENV.fetch('GITHUB_APP_ID'), ENV.fetch('GITHUB_APP_SECRET'), scope: 'user:email'
# ... rest omitted
end
Inside of a .env file, add the required env vars with the following:
At this point, we need to configure Rails to load in the env vars in development.
Setting up dotenv-rails
We need to update the config/environments/development.rb to make use of dotenv-rails:
Add in Dotenv::Railtie.load to config/environments/development.rb after the require statements.
Updating the User record
We need to configure our User model to make use of the Omniauth capability.
Update the app/models/user.rb class:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: %i[github]
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]
end
end
end
The from_omniauth method that we added will be called from our callbacks controller.
Running the migrations for OmniAuth
Now that we have configured the User model, then next step is to generate a migration to map the provider and user ID.
# Adding in the OmniAuth migration
# @see https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview
$ bin/rails g migration AddOmniauthToUsers provider:string uid:string
$ bin/rails db:migrate
Setting up our callback controller
At this point, we need to create a new controller. We will do this one manually.
# Create the users folder and add the controller file
$ mkdir app/controllers/users
$ touch app/controllers/users/omniauth_callbacks_controller.rb
Under app/controllers/users/omniauth_callbacks_controller.rb we add the following:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
# See https://github.com/omniauth/omniauth/wiki/FAQ#rails-session-is-clobbered-after-callback-on-developer-strategy
skip_before_action :verify_authenticity_token, only: :github
def github
# You need to implement the method below in your model (e.g. app/models/user.rb)
@user = User.from_omniauth(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: "Github") if is_navigational_format?
else
session["devise.github_data"] = request.env["omniauth.auth"].except(:extra) # Removing extra as it can overflow some session stores
redirect_to new_user_registration_url
end
end
def failure
redirect_to root_path
end
end
The github method is invoked when the user logs in with GitHub.
Updating our router
We need to ensure that omniauth_callbacks has the controller configured for Devise.
We can do this in config/routes.rb by updating the line devise_for :users to devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }.
# config/routes.rb
Rails.application.routes.draw do
get 'session/index'
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
resources :session, only: [:index]
resources :home, only: %i[index create]
# Defines the root path route ("/")
root 'home#index'
end
Adding in a GitHub login link
Let's add in an un-styled link for logging in with GitHub at app/views/devise/sessions/new.html.erb to the bottom of the file.
The if devise_mapping.omniauthable? block is what was added as a helper.
Logging in with GitHub
At this stage, we can run bin/dev to get the server going to test out login.
Head to localhost:3000/users/sign_in (or logout if you are currently signed in). At the bottom, you will now see a "Sign in with GitHub" option.
Sign in with GitHub option
Click on it and it should take you through the GitHub authorization flow.
Once completed, you will be logged in.
If you run the Rails console with bin/rails c, you can check your users to see what was added:
irb(main):001:0> User.last
(1.4ms) SELECT sqlite_version(*)
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #<User id: 3, email: "[REDACTED]", created_at: "2022-03-07 23:05:24.557261000 +0000", updated_at: "2022-03-07 23:05:24.557261000 +0000", provider: "github", uid: "[REDACTED]">
Summary
With this part, we introduced some simple steps to setting up our Rails + Devise app to make use of OAuth for authenticating a user with their GitHub accounts.
In the next part, we will be adding reCAPTCHA to our app to help add a layer of protection against spam.