How to Sign in with Twitter using Devise, Omniauth, and Ruby on Rails
Tags: rails, ruby, omniauth, tutorial
Published: 2019-10-31 19:50:48

---
title: How to Sign in with Twitter using Devise, Omniauth, and Ruby on Rails
published: true
date: 2019-10-12 19:14:41 UTC
tags: rails, ruby, omniauth, tutorial
canonical_url: https://web-crunch.com/sign-in-with-twitter-using-devise-omniauth-ruby-on-rails/
---

Welcome to another installment in my Let’s build with Ruby on Rails: Extending Devise series. This tutorial teaches you how to sign in with Twitter using Devise, Omniauth, and Ruby on Rails.

{% youtube 6Wq9EsgPNAY %}

### Getting started

If you would like to use my Kickoff Tailwind template you can [download it here](https://github.com/justalever/kickoff_tailwind)

[Download the source code](https://github.com/justalever/devise_sign_in_with_twitter)
### Create a new app

```bash
$ rails new devise_sign_in_with_twitter -m kickoff_tailwind/template.rb

```

The command above assumes you have downloaded the [kickoff\_tailwind repo](https://github.com/justalever/kickoff_tailwind) and have the folder name `kickoff_tailwind` in the same directory as the app you are creating.

We’ll reference these gems in this guide. You may already have Devise installed if you used my kickoff\_tailwind template.

### Gemfile:

```ruby
gem 'devise'
gem 'omniauth'
gem 'omniauth-twitter'

```

### Migrations

Generate a new migration for the user model. It will have two fields called `provider` and `uid`. Both are strings.

```bash
rails g migration AddOmniauthToUsers provider:string uid:string
rails db:migrate

== 20191005160517 AddOmniauthToUsers: migrating ===============================
-- add_column(:users, :provider, :string)
   -> 0.0028s
-- add_column(:users, :uid, :string)
   -> 0.0007s
== 20191005160517 AddOmniauthToUsers: migrated (0.0037s) ======================

```

### Devise configuration

We need to create a new application on [https://developer.twitter.com](https://developer.twitter.com). This process isn’t 100% straight-forward but is possible. There’s an application step you will need to go through.

To get the API keys shown below there is a _process_ for Twitter. You need to go through an application phase but once done there should be a `Keys and Tokens` tab within your Twitter developer app.

Once you have your keys you can add them to your Application Credentials. Run the following:

```bash
$ rails credentials:edit

```

If you get some feedback about what editor you would like to use, you’ll need to re-run the script with a editor of your choice. I prefer Visual Studio code.

```bash
No $EDITOR to open file in. Assign one like this:

EDITOR="mate --wait" bin/rails credentials:edit

For editors that fork and exit immediately, it's important to pass a wait flag,
otherwise the credentials will be saved immediately with no chance to edit.

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

```

We need to add a new configuration to our devise configuration next. Using the credentials we just added we pass those through a special ominauth twitter configuration.

```ruby
# app/config/initializers/devise.rb

Devise.setup do |config|
  ...
  config.omniauth :twitter, Rails.application.credentials.fetch(:twitter_api_public), Rails.application.credentials.fetch(:twitter_api_secret)
  ...
end

```

### Make User model `omniauthable`

We need to modify our existing Devise install to account for the new omniauth logic. Doing so happens in the User model. You can provide multiple providers depending on your app. For the sake of brevity, I’m sticking to Twitter for now.

```ruby
# app/models/user.rb

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[twitter] # add these two! 
end

```

With this added, Devise will create methods for us for each provider we pass. It does **not** create the `_url` methods like other link helpers typically do.

```
user_{provider}_omniauth_authorize_path
user_{provider}_omniauth_callback_path

```

### OmniAuth routing

With the model in place, our routing needs some attention. We need to tap into the Devise omniauth callbacks controller to do some logic there.

Change the routes file to the following:

```ruby
# config/routes.rb
require 'sidekiq/web'

Rails.application.routes.draw do
  authenticate :user, lambda { |u| u.admin? } do
    mount Sidekiq::Web => '/sidekiq'
  end

  devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
  root to: 'home#index'

end

```

\_Note: this guide assumes you are using my `kickoff_tailwind` template. Your routes file may differ from the above if not.

### OmniAuth Controllers

We are passing the path in which the OmniAuth Callbacks controller exists in our `routes.rb` file. Let’s create that folder and file next inside `app/controllers/users`

```ruby
# app/controllers/users/omniauth_callbacks_controller
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
    def twitter
    # 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: "Twitter") if is_navigational_format?
    else
      session["devise.twitter_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end

end

```

### More logic in the user model

There’s a method called `from_omniauth` we haven’t defined yet but referenced in the controller. We’ll add this method to the user model:

```ruby
# app/models/user.rb

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[twitter]

  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.username = auth.info.nickname # assuming the user model has a username
    #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

end

```

The method above will try to find any existing user or create on if the user doesn’t exist yet. While doing so we pass in information such as `email`, `name`, and an automatic password made possible by Devise. Here you can also pass and `image` if you have an avatar of some type on your user model already. For this guide, I will skip that step.

### Adding a “Sign in with Twitter” link

If you’re using my kickoff\_tailwind application template you should already have a view for the login screen. We’ll make a small change to add a new “Sign in with Twitter” button. Devise ads this dynamically for us but I wanted a more refined style.

```html
<!-- app/views/devise/sessions/new.html.erb -->
<% content_for :devise_form do %>
  <h2 class="heading text-4xl font-bold pt-4 mb-8">Log in</h2>
  <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>

  <div class="mb-6">
    <%= f.label :email, class:"label" %>
    <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "input" %>
  </div>

  <div class="mb-6">
    <%= f.label :password, class:"label" %>
    <%= f.password_field :password, autocomplete: "current-password", class: "input" %>
  </div>

  <div class="mb-6 flex">
    <% if devise_mapping.rememberable? -%>
      <%= f.check_box :remember_me, class:"mr-2 leading-tight" %>
      <%= f.label :remember_me, class:"block text-grey-darker" %>
    <% end -%>
  </div>

  <div class="mb-6">
    <%= f.submit "Log in", class: "btn btn-default" %>
  </div>

  <div class="mb-6">
    <%= link_to "Sign in with Twitter", user_twitter_omniauth_authorize_path, class: "btn bg-blue-500 text-white w-full block text-center" %>
  </div>

<% end %>

<hr class="border mt-6" />

<%= render "devise/shared/links" %>
<% end %>

<%= render "devise/shared/form_wrap" %>

```

All that has changed here is the addition of this line:

```html
 <div class="mb-6">
    <%= link_to "Sign in with Twitter", user_twitter_omniauth_authorize_path, class: "btn bg-blue-500 text-white w-full block text-center" %>
  </div>

```

And we are left with some quick design like so:

![https://i0.wp.com/i.imgur.com/uk9PxET.png?ssl=1](https://i0.wp.com/i.imgur.com/uk9PxET.png?ssl=1)

Clicking this should hopefully get us what we are after. But instead I get a `OAuth::Unauthorized` message. After some googling I found out that we need to have a series of callback urls defined in your Twitter developer account.

![https://i1.wp.com/i.imgur.com/MLrV86f.png?ssl=1](https://i1.wp.com/i.imgur.com/MLrV86f.png?ssl=1)

The URL schemes below worked for me.

![https://i2.wp.com/i.imgur.com/PI16v6z.png?ssl=1](https://i2.wp.com/i.imgur.com/PI16v6z.png?ssl=1)

Now we can try clicking “Sign in with Twitter” again.

![https://i2.wp.com/i.imgur.com/R4QJ9x4.png?ssl=1](https://i2.wp.com/i.imgur.com/R4QJ9x4.png?ssl=1)

Success! Now we can authorize the app and hopefully be redirected back to our demo Rails application.

But now I get a new error that has to do with our session size. This is a warning when your Cookie size is over the 4K limit. To get around this we can harness a gem called ‘activerecord-session\_store’

![https://i2.wp.com/i.imgur.com/ZvwzRPW.png?ssl=1](https://i2.wp.com/i.imgur.com/ZvwzRPW.png?ssl=1)

Add that to your gemfile

```ruby
gem 'activerecord-session_store'

```

and run `bundle install`

Then we need a new migration:

```bash
rails generate active_record:session_migration
rake db:migrate

Running via Spring preloader in process 6189
      create db/migrate/20191005165228_add_sessions_table.rb
Running via Spring preloader in process 6203
== 20191005165228 AddSessionsTable: migrating =================================
-- create_table(:sessions)
   -> 0.0022s
-- add_index(:sessions, :session_id, {:unique=>true})
   -> 0.0015s
-- add_index(:sessions, :updated_at)
   -> 0.0018s
== 20191005165228 AddSessionsTable: migrated (0.0056s) ========================

```

Finally we need to add a new `session_store.rb` file to our `config/initializers` folder:

```ruby
# config/initializers/session_store.rb
Rails.application.config.session_store :active_record_store, :key => '_my_app_session'

```

Inside you can copy and paste this one-liner. Feel free to name your key something more descriptive.

Be sure to restart your server if you haven’t.

Try logging in again. This time we don’t get errors! But what we do get it redirected to the Sign Up view.

This is issue most likely do to the current `null: false` constraint on the users table and email column:

```ruby
# db/schema.rb

create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    ...

```

We need to swap that to true. Let’s do so with a new migration:

```bash
rails g migration changeEmailOnUsers

```

Inside that file add the following:

```ruby
class ChangeEmailOnUsers < ActiveRecord::Migration[6.0]
  def change
    change_column :users, :email, :string, null: true
  end
end

```

Then run:

```bash
rails db:migrate

```

Restart your server and try to “Sign in with Twitter” again. Your `schema.rb` file should remove the `null:` method entirely if all goes correctly.

### A gotcha (with Twitter)

If you try logging in currently you will be redirected to `localhost:3000/users/sign_up`. This is because there’s some behind the scenes validation going on with the Devise gem. Sadly, no errors or logs tell us what has happened.

I searched for some answers here and found out that. Twitter doesn’t exactly require an email to create an account via API. Our Devise configuration requires an e-mail to be present. To override this (for now) you can add this method in the user model.

```ruby
# app/models/user.rb
class User < ApplicationRecord
  ...

  def email_required?
     false
  end
end

```

This tells Devise to make emails not required for authentication.

Logging in now _should_ work

![https://i0.wp.com/i.imgur.com/oWxgCur.png?ssl=1](https://i0.wp.com/i.imgur.com/oWxgCur.png?ssl=1)

Sweeet! It works.

### Wrapping up

My implementation here is very rigid but gets the job done. If you want to allow users to sign in with multiple providers it might make sense to automate some of the methods in the controller and model layer. You could even go pretty deep with configuration layer automation so a user enters their API keys and preferred providers and the rest is automatic. We did something similar on [Jumpstart](https://jumpstartrails.com)

Coming up I’ll introduce new providers and extend the Devise gem even more. Stay tuned!

### Shameless plug time

[![](https://i1.wp.com/web-crunch.com/wp-content/uploads/2019/01/YouTube-Promo3.jpg?resize=3840%2C2160&ssl=1)](http://hellorails.io)

I have a new course called [Hello Rails](https://hellorails.io). Hello Rails is a modern course designed to help you start using and understanding Ruby on Rails fast. If you’re a novice when it comes to Ruby or Ruby on Rails I invite you to check out the site. The course will be much like these builds but a super more in-depth version with more realistic goals and deliverables. [Download your copy today!](https://hellorails.io/)!

Follow [@hello\_rails](https://twitter.com/hello_rails) and [myself @justalever](https://twitter.com/justalever) on Twitter.

The post [How to Sign in with Twitter using Devise, Omniauth, and Ruby on Rails](https://web-crunch.com/sign-in-with-twitter-using-devise-omniauth-ruby-on-rails/) appeared first on [Web-Crunch](https://web-crunch.com).