Rails 6: How to implement a sign in with Github
Tags: Ruby, Tutorial, Ruby on Rails, Rails, Oauth

In this story I show you how you allow your users to authentication with the Github’s OAuth 2.0 interface to access your application.

Adding the omniauth gem

We start with a simple Rails 6 application.

rails _6.0.3.4_ new --database=postgresql --skip-turbolinks --skip-test github_sign_in && cd github_sign_in bundle exec bin/setup

For the OAuth 2.0 part we use the omniauth-github gem. This implements the Github strategy for the multi-provider authentication framework omniauth. Later you can add other strategies like Facebook, Google, Medium and many more, too.

bundle add 'omniauth-github' -v 1.4.0

OAuth 2.0 requires you to have a client ID, a client secret and a callback URL. Let’s create these credentials for the development environment. It’s required to follow the same steps for other environments like production, too.

Github allows you to add a new application at https://github.com/settings/developers. For the development environment the Homepage URL is http://localhost:3000 and the Authorization callback URL is http://localhost:3000/auth/github/callback . Fun fact: Github does not allow your application to start with Github or Gist for security reasons.

Finally you need to press “Generate a new client secret” once you registered the application. Add the client ID and client secret to your the encrypted credential store of Rails with bundle exec rails credentials:edit :

github_client_id: some-client-id

github_client_secret: some-client-secret

Starting with the user model

Now we can start with the back-end logic in the Rails application. We first create a migration, that adds a users table to the database. The uid is the unique identifier Github returns once a user has successfully authenticated.

bundle exec rails generate migration create_users uid: uniq email nickname bundle exec rake db:migrate

Then add the user model app/models/user.rb to the application.

class User < ApplicationRecord



validates :uid, :email, :nickname, presence: true

validates :uid, uniqueness: true



end

We also need some mechanism to find or create a user with the callback params from Github. Personally I like to keep the main models slim, therefore I create a second model app/models/user/authenticable.rb that inherits from the User model.

class User::Authenticable < ActiveType::Record[User]



def self.authenticate(authorize_params)

user = find_by(uid: authorize_params.fetch('uid'))



if user.present?

user

else

create!(

uid: authorize_params.fetch('uid'),

email: authorize_params.fetch('info').fetch('email'),

nickname: authorize_params.fetch('info').fetch('nickname')

)

end

end



end

The expression ActiveType::Record[User] comes form the active_type gem and allows the User::Authenticable to behalf just like a User with the extension of our extracted class method authenticate .

bundle add 'active_type' -v 1.3.2

Implementing a sessions controller

To allow our controllers to authenticate a user, we create the app/controllers/session_controller.rb . When a user follows the new action, she gets redirected to Github. Github itself authenticates the user and redirects her back to our create action if the authentication was successful. All relevant attributes are stored in the omniauth.auth key of the request hash.

class SessionController < ApplicationController



def new

# This route is catched by the Omniauth Middleware and is invisible to rake routes

redirect_to '/auth/github'

end



def create

user = User::Authenticable.authenticate(authorize_params)

session[:uid] = user.uid

redirect_to root_path

end



def destroy

session.delete(:uid)

redirect_to root_path

end



private



def authorize_params

request.env.fetch('omniauth.auth')

end



end

Within our routes config/routes.rb we specify the route for a sample page and the authentication paths.

Rails.application.routes.draw do

root 'dashboard#show'

get :dashboard, controller: :dashboard_controller, action: :show get 'session/new', as: :sign_in

delete 'session', action: :destroy, controller: 'session', as: :sign_out

get '/auth/github/callback', action: :create, controller: 'session'

end

Configuring the omniauth-github gem

For the omniauth middleware an initializer in config/initializers/omniauth.rb is required. It declares the credentials for the authentication and the attributes scope we want to retrieve from Github. For our example it is enough to retrieve the email attribute. The uuid and nickname are part of the default scope for Github so we can skip them. It might be enough in the beginning to raise an exception once the authentication fails. Later you can show a nice error page to user instead.

# In development the callback URL is http://wintermute.vcap.me:3000/

Rails.application.config.middleware.use OmniAuth::Builder do

provider(

:github,

Rails.application.credentials.github_client_id,

Rails.application.credentials.github_client_secret,

scope: 'user:email'

)

end



OmniAuth.config do |config|

# Disable the redirect to /auth/failure and always raise

config.failure_raise_out_environments = ['test', 'development', 'staging', 'production']

end

Finishing the application with a test screen

In the end we need to finish the dashboard#show view, that we already declared in our routes. Therefore create a simple dashboard controller in app/controllers/dashboard_controller.rb with the following content.

class DashboardController < ApplicationController def show

end end

Add a view in app/views/dashboard/show.html.erb , that shows the sign in and sign out buttons + the nickname of the user once authenticated.

<% if signed_in? %>

<%= link_to "Sign out #{current_user.nickname}", sign_out_path, method: :delete %> <% else %>

<%= link_to 'Sign in', sign_in_path %>

<% end %>

The used methods signed_in? and current_user in the show view can be added as helper method in app/helpers/session_helper.rb .

module SessionHelper def signed_in?

session[:uid].present?

end def current_user

User.find_by!(uid: session[:uid]) if signed_in?

end end