Devise and LinkedIn authentication

Ruby on Rails / Rubygems

In this article, I will use Ruby 2.5 and Rails 5.1.4 – the newest versions of both tools at the moment of writing this article. Let’s start with adding devise gem to our Gemfile file:

gem 'devise'

Now we have to run bundle install in order to install gem on our machine. However, installation is not yet finished. We have to generate devise initializer file and translation file by running following command from our command line:

rails generate devise:install

Model generation

We can now generate our model. We will name it User as it’s a common name for model with authorization data.

rails generate devise user

Devise will generate for us model class, migration, and blank tests. Our User model has following contents:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end

Since we want our application to allow only authorization with LinkedIn we would remove not needed code:

class User < ApplicationRecord
  devise :trackable, :omniauthable
end

Our migration should have following contents:

# frozen_string_literal: true

class DeviseCreateRecruiters < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      t.string :email,              null: false, default: ""

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.inet     :current_sign_in_ip
      t.inet     :last_sign_in_ip

      # LinkedIn
      t.string :provider
      t.string :uid, unique: true

      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
  end
end

We don’t want to allow the user to authenticate using his email and password but we still want to keep using email to use it later in our application. To create a new table in our database we have to run migration:

bundle exec rake db:migrate

If you noticed that you left some columns that you don’t need, you can rollback migration and correct your file and then run migration once again:

bundle exec rake db:rollback STEP=1

Columns provider and uid will be used for LinkedIn authentication with OmniAuth strategy.

LinkedIn configuration

We would use omniauth-linkedin gem for our authorization so let’s install it first. Add a new line to your Gemfile and then run bundle install:

gem 'omniauth-linkedin'

You would need now a consumer_key and consumer_secret keys for your app. If you don’t have an app yet you can check this article and create one.

Open and edit Devise gem initializer located in config/initializers/devise.rb and put your credentials:

config.omniauth :linkedin, "consumer_key", "consumer_secret"

We can now update our User model and let it know that we want to use LinkedIn as authorization provider:

class User < ApplicationRecord
  devise :trackable, :omniauthable, omniauth_providers: %i[linkedin]
end

Routing

If we have devise_for :users entry in our config/routes.rb file then Devise will automatically add two paths:

user_omniauth_authorize_path(provider)
user_omniauth_callback_path(provider)

We can now add a link to start LinkedIn authorization:

<%= link_to "Sign in with LinkedIn", user_linkedin_omniauth_authorize_path %>

Clicking on this link will redirect you to the LinkedIn authorization page. We have to prepare now our controller to receive a request and authorize given user on our application side.

Authentication controller

To handle authorization action we would create a separated controller. We will name it AuthorizationsController. In order to process LinkedIn request, we have to add two methods there: linkedin and failure. The first method is responsible for handling success response and the second for handling failure.

class AuthorizationsController < Devise::OmniauthCallbacksController
  def linkedin
  end

  def failure
    redirect_to root_path
  end
end

to make it work we also have to update our routes file and tell Devise which controller we want to use for LinkedIn authorization:

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

Let’s implement now code responsible for creating and finding users:

def self.from_omniauth(auth)
  where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
    user.email = auth.info.email
  end
end

so we can update our controller and create fully working authorization:

class AuthorizationsController < Devise::OmniauthCallbacksController
  def linkedin
    @user = User.from_omniauth(request.env["omniauth.auth"])

    sign_in_and_redirect @user, event: :authentication
  end

  def failure
    redirect_to root_path
  end
end

and that’s it! We created very simple and basic application that allows the user to authorize via the LinkedIn network. We save only e-mail. You may want to collect other data such as name or image. Is up to you, the possibilities are endless.

When the user is authorized you can access it via current_user variable in controller and views.