start to finish Devise with GitHub OmniAuth authentication — Ruby on Rails

Sascha Kala
9 min readNov 24, 2020
A screenshot of the Devise User model file.

So you’re creating a Ruby application (or you just want to pass your Flatiron Rails portfolio project) and you don’t want to write your own authentication: I don’t blame you. Devise is pretty sweet, as long as you set it up right, and does a lot of the work for you #DevsAreLazy (and #CamelCaseHashtagsAreScreenReaderFriendly — lets all be better about considering others’ access needs, shall we?)

To set your expectations accurately: this guide has 28 steps. And, while I know that sounds like a lot, it does walk you through everything you need to know in order to:

  1. set up Devise in a new Ruby app (numbers 1–12)
  2. add custom attributes to your user model and override the Devise controller (numbers 13–16)
  3. set up OmniAuth to authenticate a new user using GitHub as the 3rd party strategy (numbers 17–28)

Let’s get started (from the very beginning):

  1. Create your new rails app (and it’s corresponding Git repo).
in your terminal:rails new very-cool-app

2. Add the Devise gem to your Gemfile

# Gemfilegem 'devise'

3. Run bundle update (if the Devise gem is already installed on your computer — if not run bundle install) to make sure the Devise gem is actually installed and to update your gemfile.lock

in terminal:bundle update

4. Install Devise in your application.

in terminal:rails g devise:install

You’ll notice that this command generated two Devise files for you within your app/config folder. It also did a lot of things that you can’t actually see in the background of your app, which is why it’s a great idea to install Devise as the first thing you do when creating a new application (cause otherwise layering all of this on top of everything else can break it).

Additionally you’ll notice that Devise printed out 4 instructions in your terminal for you to follow. These are necessary steps in order for Devise to work properly: 1) define a default URL; 2) define a root_url; 3) set flash messages in your application layout; and 4) set up Devise views. I’ll be covering 2, 3, and 4 because number 1 is only relevant if you have a mail server installed on your computer (and this didn’t pertain to my project).

Note: it was at this point in my project, after running rails g devise:install , that I created one of my other models (and the corresponding controller, table, resource routes, and an index view page) for the sake of being able to create a root_url to satisfy Devise’s needs. Devise automatically redirects the user to the root_url that you set once the user has successfully logged in.

If you’re setting up Devise as the first thing you do in your new app, take some time now to set up one of your other models so that you can continue with step 5 below.

5. Define a root route (number 2 from above), remembering that it’s good practice to keep your routes in top-down order from most to least specific.

# config/routes.rbroot to: "model-name#index"

6. Add flash messages to your application layout (number 3 from above). The simplest way to do this is to use the code that Devise provided for you after running rails g devise and copy/paste it into the body of your application layout above <%=yield%> to make sure the errors render at the top of your page once it’s loaded.

#app/views/layouts/application.html.erb<body>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%=yield%></body>

7. Generate the Devise User model.

in terminal:rails g devise user

This command creates a series of files, including your User model and migration, as well as adding routes (if you take a peek at your routes.rb file you’ll notice that the Devise user routes show up as devise_for :users). (This command also creates a hidden controller and view files — but we’ll talk more about the views and how to overwrite the controller in a bit!)

8. Check out the migration file Devise created for you. You’ll notice that there’s a whole page of possible attributes, many of which appear commented out. Feel free to uncomment any user attributes you might want in your app — but make sure to leave Recoverable and Rememberable turned on or your app will break ;)

9. Time to: rails db:migrate

10. It might sound inconceivable given that it feels like you haven’t actually built anything, but your sign up will now work. Yep! Go ahead and check it out by starting your server and heading to localhost:3000/users/sign_up (in the event that you haven’t, make sure that you set up your root route before testing this).

If you’re curious to see what other routes Devise created for you, check localhost:3000/rails/info/routes for the full list.

11. Now let’s generate those Devise views (number 4 from Devise’s todo list above).

in terminal:rails g devise:views

What’s actually happening when you run this command is that you’re generating the views that already exist in the background of your application (the ones that made it possible for your sign up page to work already!), so that they can be edited and changed. You’ll notice that you now have an app/views/devise folder.

12. This one’s a little sugar & spice: but let’s build out a helper nav using the helper methods included with Devise as well as the generic routes it created for us.

Start by creating a partial _nav.erb file in the app/views/layouts folder. Then add the following code:

<ul><% if user_signed_in? %>
<li><%= link_to ‘Edit your account’, edit_user_registration_path %></li>
<li><%= link_to ‘Sign out’, destroy_user_session_path, method: :delete %></li>
<% else %>
<li><%= link_to ‘Sign in’, new_user_session_path %></li>
<li><%= link_to ‘Sign up’, new_user_registration_path %></li>
<%end %>
</ul>

user_signed_in? is one of the helper methods Devise ships with: it’s a boolean that returns true or false depending on whether a user is logged in. The URL helpers used here come directly from the ones Devise created for you (see them all at localhost:3000/rails/info/routes).

Don’t forget to render your partial by adding it to the body of your application layout:

#app/views/layouts/application.html.erb<body>
<%= render 'layouts/nav'%>
...
</body>

Custom attributes & overriding the Devise controller:

Note: if you don’t want to add custom attributes to your User and so don’t need to override your Devise controller, you can skip to #

13. Add custom attributes to your User model (for example: name, username, etc.).

Create a migration with the attributes you know you want (tip: Github user data comes in with a ‘name’ attribute so, since we’re authenticating with GitHub, I’m adding that to our User table here).

in terminal:rails g migration AddColumnsToUser name username

Check your migration file and then run rake db:migrate .

14. Time to override the User controller. Doing this will allow you to add the new user attributes you just created to the User’s strong params so that the information can actually be collected and stored upon sign up.

First, manually click to create a new controller file: registrations_controller.rb (rails g controller will generate a bunch of extra files that you don’t need and will have to delete — I don’t recommend it for this). Your new controller will inherit from the Devise controller (which, in turn, inherits from ApplicationRecord).

Add the following code to your new controller:

class RegistrationsController < Devise::RegistrationsControllerprivate
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
def account_update_params
params.require(:user).permit(:email, :password, :password_confirmation, :current_password)
end
end
end

As you can see, the RegistrationsController is inheriting from Devise’s controller. The params listed above are the ones Devise has already set: don’t change them, simply add the attributes you created in step 13 to make sure your custom params are also included.

My params looked like this:

 def sign_up_params
params.require(:user).permit(:name, :username, :email, :password, :password_confirmation)
end
def account_update_params
params.require(:user).permit(:name, :username, :email, :password, :password_confirmation, :current_password)
end

15. Whitelist your new RegistrationsController, otherwise Devise won’t know where to find it. We’ll be adding to the devise_for :users line of your routes.rb file.

The syntax is as follows:

devise_for :users, :controllers => {registrations: ‘registrations’}

In this case our new controller is actually called Registrations, otherwise you’d want to write: :controllers => {registrations: 'name_of_your_custom_registrations_controller'}

16. Add fields to both the new and edit forms in order to be able to collect information.

Navigate to app/views/devise/registrations/new.html.erb. To keep things super simple (and preserve the formatting), copy/paste one of the form fields Devise created for you and then change the attribute name accordingly. Repeat as many times as needed, making sure that when you’re done only one of the fields has autofocus: true turned on. (Devise ships with autofocus: true being turned on for the :email attribute, so either leave it there or move it around to whatever attribute field you choose to render first on the page.)

Repeat the above for the edit form.

Installing OmniAuth:

17. Add the OmniAuth gems to your Gemfile.

#Gemfilegem ‘omniauth’
gem ‘omniauth-github’

Every strategy available for OmniAuth has their own strategy-specific gem. Here is the complete list. If you’re authenticating with something other than GitHub make sure to use that strategy’s specific gem instead.

18. bundle install

19. Create a new OAuth app in GitHub.

  • head to github.com
  • click on the drop down menu under your profile
  • go to settings > developer settings > OAuth Apps > New OAuth App
  • Give your app a name and set the homepage URL (in my case: http://localhost:3000/)
  • set the callback URL (for GitHub when used with Devise it’s: http://localhost:3000/users/auth/github/callback). The standard format for OAuth callback URLS is auth/application/callback (Devise adds the /users/ before /auth/) but some applications are more temperamental than others (ahem Google, Facebook, and Twitter) and have their own unique variation on the standard. Check out the OAuth documentation if you need to figure out the callback for another strategy.
  • click ‘Register Application’ (this will take you to a page that shows your Client ID and Client Secret)

20. Update your .gitignore

Before I add private, identity theftable information to my app I like to make sure that the file I’m adding it to isn’t pushing to GitHub. So let’s do that.

  • navigate to config/initializers/devise.rb (this is where your ID and secret will ultimately go)
  • Add a random line of commented-out text to this file (if there are no changes it won’t get pushed)
  • head to your .gitignore and add the line/config/initializers (I pasted mine under the heading: # Ignore master key for decrypting credentials and more)
  • git add . / git commit -m "testing gitignore" / git push
  • make sure your config/initializers file is not showing up on your repo

If your initializers file did get pushed and is now part of your Git repo, do the following to remove it before moving on:

  1. git rm -r config/initializers --cached
  2. git add -u
  3. git commit -m "removing initializer files"
  4. git push

21. Add Client ID and Client Secret to your app.

  • again navigate to config/initializers/devise.rb
  • search the page for “omni”
  • uncomment the following line of code:config.omniauth :github
  • replace APP_ID with your Client ID number (taken from GitHub) — make sure to leave it within the quotes
  • replace APP_SECRET with Client Secret —also leave within quotes (if you don’t initially see a secret: “Generate a new client secret”)

22. Manually create a callbacks_controller.rb file and set it up to inherit from Devise

class CallbacksController < Devise::OmniauthCallbacksControllerend

23. Within this controller we’ll create a github method that’ll create a user from the GitHub data that OAuth provides:

def github
@user = User.from_omniauth(request.env[“omniauth.auth”])
sign_in_and_redirect @user
end

A few things to note: sign_in_and_redirect is a method built into OAuth. from_omniauth is a method we’re about to define in the User model.

24. Whitelist the CallbacksController in routes.rb so that Devise can find it. To do this we’ll again be updating the devise_for :users line of code. The syntax is as follows:

devise_for :users, :controllers => {registrations: ‘registrations’, omniauth_callbacks: ‘callbacks’ }

25. Make the user omniauthable by adding to the line of code that starts with devise

devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :omniauthable

26. Create a migration to add ‘provider’ and ‘uid’ to User model.

The provider signifies the 3rd party strategy you’re using to authenticate; the UID is the identifying number given by that provider.

in terminal:rails g migration AddOmniauthToUsers provider uid
rake db:migrate

27. Create a self.from_omniauth method in your User model

This is where we’ll actually assign the data that OAuth is grabbing for us to different user attributes. GitHub data comes in in the form of a nested hash which gives us an easy way to target the info we want. Use Pry to play around and figure out which GitHub data to set to which user fields.

My method looked as follows:

def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user |
user.provider = auth.provider
user.name = auth.info.name
user.username = auth.info.nickname
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
end
end

28. Start your server and check out your sign up page — you’ll notice that a link to login with GitHub already exists! Turns out that Devise is set up to automatically render 3rd party login links for users that are omniauthable, with no extra effort on your part.

You did it! You have successfully made it through this XL blog post. (Oh and you also successfully set up Devise with OAuth, that too.)

A few miscellaneous tidbits:

  • In addition to user_signed_in? Devise has a number of handy helper methods. Here is the full documentation if you want to see all the methods Devise makes available to you, and this is a blog post that details the 6 most useful helpers (you have to scroll down).
  • Bcrypt is built into devise so your passwords are being salted and hashed for you.

I hope this step-by-step was helpful to you! If you benefitted I’d love if you gave me a clap or two. Yikes, rhyme. Also on twitter @saschakala

--

--

Sascha Kala

Software engineer and they/them tech femme; artist // digital creator