🌞 🌚

Add Omniauth GitHub to Your Rails App on Top of Devise

🗓 - ⏱ 7 minute read

Last update: 16 July 2018

As I’ve moved into the last three weeks of @lewagon, my teammates and I have been working on marketplace apps before getting to our final projects.

The need to authorize people to sign-up with GitHub quickly arise. Here’s a step-by-step tutorial on how to do it when you already use Devise to handle authentification.

Basic set up

Before working on your app, you need to set some things up.

Create a application on Git Hub

First, we need to create a OAuth app on GitHub. Go to your profile, then developper settings and click New OAuth App.

Give your application a name, a URL and a callback URL. What if you want to work with localhost? Glad you asked.

For your homage URL: http://http://localhost:your_port will work perfectly

For the callback URL, you can’t use http://http://localhost:your_port because GitHub needs you to specify an address that can be publicly access. So, what do we do?

ngrok to the rescue

ngrok exposes your local web server through a public URL. So we need to install ngrok and have it listening to our local server. We’ll then be able to get a public URL and give it to GitHub.

First, run in your terminal:

brew install ngrok

Then run:

ngrok http your_port

You’ll get:

ngrok by @inconshreveable                                       (Ctrl+C to quit)

Session Status                online
Session Expires               7 hours, 59 minutes
Version                       2.2.8
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://3aa8f0f7.ngrok.io -> localhost:3000
Forwarding                    https://3aa8f0f7.ngrok.io -> localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                 0       0       0.00    0.00    0.00    0.00

And there you have your callback URL: http://3aa8f0f7.ngrok.io -> localhost:3000.

Do not forget to append your callback URL with /users/auth/github/callback.

Your development callback URL should look like this:

http://3aa8f0f7.ngrok.io/users/auth/github/callback

We’ll need to change these two URLs once we move into production. ⚠️ Be careful to use the same protocol in your app URL and callback URL. Using http in one and https in another with result in a URL mismatch.

Save your API keys in your Rails app

We don’t want our API keys being pushed to GitHub. So I’ll use the figaro gem to store these in a secure file.

# Add figaro to your gem file@
gem 'figaro'
# In your terminal
bundle install

Figaro creates a config/application.yml to put all your API keys and adds this file in .gitignore.

Copy/paste your secret keys in config/application.yml

development:
  GITHUB_ID: 8***********************b
  GITHUB_SECRET: 4***********************************************3

Don’t forget to tell devise to use these keys in config/initializers/devise.rb.

config.omniauth :github, ENV['GITHUB_ID'], ENV['GITHUB_SECRET'], scope: 'user,public_repo'

Configure Omniauth GithHub with Devise

Since we use Devise to handle authentification, we don’t want to write config in config/initializers/omniauth.rb.

Let’s get down to it!

The first step is to had the Omniauth gem to your app. Go to your Gemfile:

gem 'omniauth-github'

Then run:

bundle install

Next step is to add the provider and uid columns to our User model. Remember we already declared the provider in our config/initializers/devise.rb.

rails g migration AddOmniauthToUsers provider:string uid:string
rake db:migrate

We go to user.rb and make our user omniauthable.

devise :omniauthable, omniauth_providers: %i[github]

With devise_for :users already in place, Devise will create two URL methods:

Add it to your view

Paste the following code to get a simple link to test it out:

<%= link_to "Sign up with GitHub", user_github_omniauth_authorize_path %>

⚠️ The symbol passed to the user_omniauth_authorize_path method should match the symbol of the provider passed to Devise’s config block.

Now, when clicking on Sign up with GitHub, people will be redirected to GitHub to give their credentials. But as for now, nothing happens when GitHub sends back the user’s data.

Add the callback to your app

Let’s go back to our config/routes.rb to tell Devise in which controller we’ll implement our callbacks:

devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }

Now we just add the file app/controllers/users/omniauth_callbacks_controller.rb:

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
end

The callback should have the same name as the provider we passed in Devise’s config block.

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
   def github
       @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"]
         redirect_to new_user_registration_url
       end
     end

     def failure
       redirect_to root_path
     end
 end

Then, from the controller, we move to our user model. Here is the code from Devise documentation:

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.name = auth.info.name   # assuming the user model has a name
     user.image = auth.info.image # assuming the user model has an image
     # If you are using confirmable and the provider(s) you use validate emails,
     # uncomment the line below to skip the confirmation emails.
     # user.skip_confirmation!
   end
 end

After changing the code above to suit my primary needs, I ran into several problems:

So, here’s my own version of the Class method based on my schema and needs:

def self.from_omniauth(auth)
     user = User.find_by(email: auth.info.email)
     if user
       user.provider = auth.provider
       user.uid = auth.uid
       user.save
     else
       user = User.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.first_name = auth.info.name.split(' ').first
         user.last_name = auth.info.name.split(' ').second
       end
     end
     unless user.avatar.present?
       photo_url = auth.info.image
       user.remote_avatar_url = photo_url # Carrierwave helper
       user.save
     end
     user
   end

What caused the second problem was that the model had two validation strategies conflicting with one another:

My user model had a validates :password, presence: true that was raising an error and preventing user from logging in. This useless line of code was caused by a lack of communication within our team. Talking more with each other would have saved us a lot of debugging ☝️.

Push into production

After testing and merging my branch on GitHub we pushed it to Heroku. It’s important at that point to go back to your GitHub app page and update the app URL and the callback URL with your domain name.

Your app URLs should now look like these:

Homepage URL: http://your_app_url.your_domain

Callback URL: http://your_app_url.your_domain/users/auth/github/callback

Also, don’t forget to give Heroku your API keys. Otherwise your production environnement will not know how to interpret config.omniauth :github, ENV['GITHUB_ID'], ENV['GITHUB_SECRET'], scope: 'user,public_repo’ in your config/initializers/devise.rb (and your users will get a magnificent 404).

Don’t forget to run:

heroku run bundle # to install the omniauth gem
heroku run rails db:migrate # to update your users schema
heroku restart

And here it is, now people can sign-up and sign-in to your app using their GitHub credentials. 🙌