DEV Community

Cover image for Omniauth with Devise
geraldarzy
geraldarzy

Posted on

Omniauth with Devise

Here is a guide to getting Omniauth setup with Devise on Rails and also some bugs that you might run into. For the remainder of the article, lets assume that you already have Devise set up with your app. De-bugging will be towards the end of the article.

To get started, include the omniauth gem in your gemfile.
gem 'omniauth-rails_csrf_protection'

You'll also want to choose from a number of different strategies that are available. Strategies, which are more commonly known as Providers in other authentication helpers, are linked to omniauth and allow us to connect to different websites and use their authentication services. In this article we'll be working with two strategies, 'Github' and 'Google'. Add these to your gemfile to work with these strategies,

gem 'omniauth-github'
gem 'omniauth-google-oauth2'
Enter fullscreen mode Exit fullscreen mode

And dont forget to bundle install

Before we get to any of the technical code part, make sure that you have the ID and Secret from your strategy as well as set the proper callback routes. In this case, we'll head over to github and google.

First lets take care of Github. Go to github and go to Settings -> Developer Settings -> OAuth Apps -> New OAuth App.

Then now you can set your Homepage URL which in this case would be http://localhost:3000. That is the default server that is used when you run rails server.

Then you must also set your Authorization callback URL to http://localhost:3000/users/auth/github/callback. The callback URL is where the strategy is directed to after the authentication process whether or not the user passes the authentication.

Once those two are both set, be sure to either grab your Client ID and Client Secret now and write it down on a notepad for easy access or just keep it handy on an open tab.

You'll want to do the same exact thing for google and you can get to the developers page through here.

Now to make use of the Client ID and Secret, we'll want to keep them in a secured file in our stack. You can either choose the .env route and use gem 'dotenv-rails or you can use the credentials.yml file that is already provided to you in the config directory of your app.
We will be going with the credentials.yml route for this article.

To open up the file and store our keys in there, go into the terminal and run

EDITOR="code --wait" rails credentials:edit

A quick gist to this line.
'code' is interchangeable with whichever IDE you're using. 'code' is for Visual Studio but if you were on Atom you'd switch that out to 'atom'.

'--wait' is telling our editor to wait until i'm done with this file AND THEN you can save and close it.

So now if you run this line in the terminal, the credentials.yml file should open up and inside you put your Client ID and Secret as so

github:
  github_client_id: #paste your id here
  github_client_secret: #paste your secret here

google:
  google_client_id: #paste your id here
  google_client_secret: #paste your secret here
Enter fullscreen mode Exit fullscreen mode

Now save and just close the file. To make sure that the file was saved, you can run this line in your terminal.
rails credentials:show

Now that you have your ID and Secret in order, we can let devise know to look for these ID and Secret right where we put them.
Go into the devise.rb file located in, config-> initializers -> devise.rb.

Now look for the Omniauth section, it should be towards the bottom of the file, but you can just quickly CMD+F to find 'omniauth'.

Now in this section you'll want to paste in

config.omniauth :github, Rails.application.credentials.dig(:github, :github_client_id),
  Rails.application.credentials.dig(:github, :github_client_secret), scope:'user,public_repo'

  config.omniauth :google_oauth2, Rails.application.credentials.dig(:google, :google_client_id),
  Rails.application.credentials.dig(:google, :google_client_secret), scope:'userinfo.email,userinfo.profile'

Enter fullscreen mode Exit fullscreen mode

These lines are telling Devise to look through the credentials file and to retrieve our info. The code itself is pretty understandable. With just a quick glance we can see that were passing in the name of the strategy with config.omniauth :github and then following up by going to the crendentials file and digging through it for a block called :github and to go into that block and find the value for :github_client_id.
It just repeats that for the :github_client_secret and the same for google. Although, notice that googles strategy name is google_oauth2 and not just google.

Now you'll need two columns added to your users table so that a user can hold a 'uid' and 'provider'. These we'll be populated once a strategy has succesfully authenticated a user.

rails g migration update_users

That should generate a new migration file for you where you can put in

    add_column(:users, :provider, :string, limit: 50, null: false, default:'')
    add_column(:users, :uid, :string, limit: 50, null: false, default:'')
Enter fullscreen mode Exit fullscreen mode

Now run the migrations and lets head over to the User Model.

With Devise already set up, you should already have something like this in your User model.

 devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
Enter fullscreen mode Exit fullscreen mode

We can go ahead and let our devise user know about omniauth by adding on a couple of things to that list.

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :omniauthable, omniauth_providers: [:github, :google_oauth2]
Enter fullscreen mode Exit fullscreen mode

In the same file, we'll add a new method to deal with the callback data that these strategies will provide.

 def self.create_from_provider_data(provider_data)
    where(provider: provider_data.provider, uid: provider_data.uid).first_or_create  do |user|
      user.email = provider_data.info.email
      user.password = Devise.friendly_token[0, 20]
    end
  end
Enter fullscreen mode Exit fullscreen mode

Lastly, we'll want to make a route for this. Since we are using Devise with Omniauth, the routes are already defined by Devise and we just have to intercept them and implement our own logic when dealing with each new strategy we add.

So head on to our routes.rb and look for devise_for :users.
We simply add a controller to this route with devise_for :users, controllers: {omniauth_callbacks: 'omniauth'}.
In that line, were telling the omniauth_callbacks to be directed towards the omniauth controller that we are about to make.

First we'll have to name the file whatever we told the callbacks to be directed to, so 'omniauth' followerd by '_controller.rb' so omniauth_controller.rb because of the naming convention. Now for the inside of the file,

class OmniauthController < Devise::OmniauthCallbacksController
   def github
       @user = User.create_from_provider_data(request.env['omniauth.auth'])
       if @user.persisted?
            sign_in_and_redirect @user
            set_flash_message(:notice, :success, kind: 'Github') if is_navigational_format?
       else
            flash[:error]='There was a problem signing you in through Github. Please register or try signing in later.'
            redirect_to new_user_registration_url
       end
    end
 def google_oauth2
       @user = User.create_from_provider_data(request.env['omniauth.auth'])
       if @user.persisted?
            sign_in_and_redirect @user
            set_flash_message(:notice, :success, kind: 'Google') if is_navigational_format?
       else
            flash[:error]='There was a problem signing you in through Google. Please register or try signing in later.'
            redirect_to new_user_registration_url
       end
    end

    def failure
        flash[:error] = 'There was a problem signing you in. Please register or try signing in later.'
        redirect_to new_user_registration_url
    end
end

Enter fullscreen mode Exit fullscreen mode

With a closer look, we can see that the action is creating a new user using the method we made and assigning it to an instance variable '@user'. And if that user was saved, redirect with a success message or redirect with an error message. After all this, you should be set to go.

De-Bugging

  • Some people run into an issue with the links to the strategy page not working. First and foremost, you have to make sure that the links are sending a POST request and not a GET request. Go into the devise views directory. views -> devise -> shared -> _links.html.erb In here is the link_to helper tag that generates the links for us. Just append a `, method: :post` to the end of this link_to and your links should now send a POST request. We're sending POST request because with this new version of omniauth, there are more safety features set in place that are only used when we send a POST request.
  • If you are having trouble with the Devise gem itself, you might need to use a specific github branch of devise depending on what date it is. `gem 'devise', github: 'heartcombo/devise', branch: 'ca-omniauth-2'` This is because the current version of devise is having problems with the new version of omniauth. This branch of devise has not been merged over to the master yet and therefore we are using it from the branch. Depending on when youre reading this, the branch might no longer exist and has been merged into the master.

Top comments (4)

Collapse
 
12 profile image
VF

Thanks for the write up.

I'm stuck on Request Phase Initiated.. Any ideas ?

Collapse
 
arjunrajkumar profile image
Arjun Rajkumar

I'm stuck too on this. Do you remember how you got past this?

Collapse
 
12 profile image
VF • Edited

I had two issues;

First, I had to monkey patch one of the methods in omniauth-apple.

Second, I ran into an issue with using Turbo which I fixed by adding data-turbo="false"

eg.

<%= button_to user_apple_omniauth_authorize_path, method: :post, data: {turbo: "false"} do %>  Sign in with Apple<% end %>

Hopefully this helps.

Thread Thread
 
yammasaimeno profile image
Eric Lang

the data: {turbo: "false"} worked for me