How to fix SSL error thrown by omniauth LinkedIn

504 Views Asked by At

I'm trying to set up an authentication via LinkedIn in the rails 5.2 application, for the same I'm referring to the documentation given by devise but I am getting the following error:

ERROR -- omniauth: (linkedin) Authentication failure! Connection reset by peer: Faraday::SSLError, Connection reset by peer

I have added these using the following gems for the configuration

  1. devise ~> 4.8.0
  2. omniauth-linkedin-oauth2 ~> 1.0.0
  3. omniauth ~> 2.0.4

I even tried running on the active domain in the production server which contains the valid SSL certificate but still, the same error is thrown.

1

There are 1 best solutions below

0
On

Some informations about LinkedIn for you:

LinkedIn no longer supports the JavaScript SDK. The recommended approach is to use OAuth 2.0 and LinkedIn's Auth APIs.

And:

LinkedIn does not support TLS 1.0. Support for TLS 1.1 has been marked for deprecation starting 02/01/2020. Please use TLS 1.2 when calling LinkedIn APIs. All API requests to api.linkedin.com must be made over HTTPS. Calls made over HTTP will fail.

Step 1: Add Jquery for Javascript library, run command:

$ yarn add jquery

Then, set content of config/webpack/environment.js:

const { environment } = require('@rails/webpacker')
const webpack = require('webpack')
environment.plugins.prepend('Provide',
 new webpack.ProvidePlugin({
   $: 'jquery/src/jquery',
   jQuery: 'jquery/src/jquery'
 })
)
module.exports = environment

Step 2: Create ssl connection by add thin gem

gem 'thin'
$ bundle install

Edit config/application.rb and add:

config.force_ssl = true

In project command line, type:

$ openssl genrsa 2048 > host.key
$ chmod 400 host.key
$ openssl req -new -x509 -nodes -sha256 -days 365 -key host.key -out host.cert

After these commands will create two file: host.key and host.cert. Then run:

$ thin start --ssl --ssl-key-file=./host.key --ssl-cert-file=./host.cert

It will run project in default address: https://0.0.0.0:3000. If you want to run on https://localhost:3000, just type:

$ thin start -a localhost --ssl --ssl-key-file=./host.key --ssl-cert-file=./host.cert

Step 3: Create Linkedin oauth2 app.

Go to link: https://www.linkedin.com/developers/

Click the button Create app, then fill the informations to App name, LinkedIn Page (have to finish it by a custom page), App logo, Terms tick box. Then click to Create app to register your app.

At Settings tab, set the domain of your app, I run with localhost so I will set https://localhost:3000.

At Auth tab, save the Client ID and Client Secret to config/application.yml (remember to run commands $ bundle exec figaro install before this) like these:

LINKEDIN_APP_ID: 86g3...sfjm
LINKEDIN_APP_SECRET: OKnb...jzSL

Then edit, type and save to part Authorized redirect URLs for your app:

https://localhost:3000/auth/linkedin/callback

Check available scopes to use in this page! Mine is r_emailaddress r_liteprofile.

At Products tab, select Sign In with LinkedIn, status will change to Review in progress. All is ok if this status disappear after refresh F5 by a while!

Step 4: Set all codes like mine. With simple config/routes.rb:

Rails.application.routes.draw do
  devise_for :users, :controllers => { :omniauth_callbacks => "omniauth_callbacks" } 

  get '/auth/linkedin/callback', to: "linkedin#callback"
  post '/auth/linkedin/url', to: "linkedin#popup"
  post '/auth/linkedin/token', to: "linkedin#get_token"
  post '/auth/linkedin/info', to: "linkedin#get_info"
  post '/auth/linkedin/out', to: "linkedin#stop"
  root to: "linkedin#home"
end

Create app/controllers/linkedin_controller.rb with content:

class LinkedinController < ApplicationController
  # Lib to get token
  require "uri"
  require "net/http"
  # Data variables set/get
  attr_accessor :client_id, :client_secret, :redirect_uri, :scope, :raise_error
  # Class variable with 2@
  @@token = ""
  # Return view linkedins/home page
  def home
    render 'linkedins/home'
  end
  # Call back from popup login window of LinkedIn site
  def callback
    Rails.logger.debug "Callback called! Params:"
    Rails.logger.debug params
    @code = params[:code]
    @state = params[:state]
    @redirect = '/auth/linkedin/callback'
    # Get token
    url = URI("https://www.linkedin.com/oauth/v2/accessToken")
    https = Net::HTTP.new(url.host, url.port)
    https.use_ssl = true
    request = Net::HTTP::Post.new(url)
    request["Content-Type"] = "application/x-www-form-urlencoded"
    host_uri = ENV['HOST']+@redirect
    request.body = "grant_type=authorization_code&code=#{@code}&client_id=#{ENV['LINKEDIN_APP_ID']}&client_secret=#{ENV['LINKEDIN_APP_SECRET']}&redirect_uri=#{host_uri}"

    response = https.request(request)
    Rails.logger.debug "response.read_body:"
    # Rails.logger.debug response.read_body
    r = JSON.parse(response.read_body)
    Rails.logger.debug r["access_token"]
    @@token = r["access_token"]

    render 'linkedins/callback'
  end
  # Config init values
  def start
    @client_id = ENV['LINKEDIN_APP_ID']
    @client_secret = ENV['LINKEDIN_APP_SECRET']
    @raise_error = 'true'
    @redirect = '/auth/linkedin/callback'
    @redirect_uri = ENV['HOST']+@redirect
    @scope = 'r_emailaddress r_liteprofile'
    @state = generate_csrf_token
  end
  # Return popup url for sign in by LinkedIn, method = POST
  def popup
    self.start
    @url = "https://www.linkedin.com/uas/oauth2/authorization?client_id=#{@client_id}&raise_errors=#{@raise_error}&redirect_uri=#{@redirect_uri}&response_type=code&scope=#{@scope}&state=#{@state}"
    # return @url
    render json: { status: 'Success', message: 'Load url for popup finished!', link: @url},status: :ok
  end
  # Get token of current account Linkedin logged
  def get_token
    Rails.logger.debug 'From get_token, @@token cache:'
    Rails.logger.debug @@token
    render json: { status: 'Success', message: 'Load token finished!', token: @@token},status: :ok
  end
  # Get basic info
  def get_info
    Rails.logger.debug 'From get_info!'
    # Create custom api linking
    fields = ['id', 'firstName', 'lastName', 'profilePicture']
    link = "https://api.linkedin.com/v2/me?projection=(#{ fields.join(',') })"

    url = URI(link)
    https = Net::HTTP.new(url.host, url.port)
    https.use_ssl = true
    request = Net::HTTP::Get.new(url)
    request["Authorization"] = "Bearer #{@@token}"
    response = https.request(request)
    Rails.logger.debug "From get_info, variable response:"
    Rails.logger.debug response
    r = JSON.parse(response.read_body)
    # r = JSON.parse(response.body)
    first_name = r['firstName']['localized']['en_US'].to_s
    last_name = r['lastName']['localized']['en_US'].to_s
    full_name = first_name + " " + last_name
    render json: { status: 'Success',message: 'Load link basic info finished!', name: full_name},status: :ok
  end
  # For logout LinkedIn, by method revoke
  def stop
    link = 'https://www.linkedin.com/oauth/v2/revoke'
    url = URI(link)
    https = Net::HTTP.new(url.host, url.port)
    https.use_ssl = true
    request = Net::HTTP::Post.new(url)
    request["Content-Type"] = "application/x-www-form-urlencoded"
    request.body = "client_id=#{ENV['LINKEDIN_APP_ID']}&client_secret=#{ENV['LINKEDIN_APP_SECRET']}&token=#{@@token}"
    response = https.request(request)
    Rails.logger.debug "Test logout linkedin!"
    render json: { status: 'Success',message: 'Log out finished!'},status: :ok
  end
  # Genereate random state
  def generate_csrf_token
    SecureRandom.base64(32)
  end
end

Note to install these gems, and we don't need any oauth2 linkedin libs:

gem 'uri'
gem 'net-http'
$ bundle install

We will exit popup LinkedIn login by this callback view app/views/linkedins/callback.html.erb:

<script>
  // Close this popup show from LinkedIn window open
  close();
</script>

Create this main view app/views/linkedins/home.html.erb:

<p>Linkedin Login Home page</p>
<button id="linkedin-login" type="button">Login</button>
<p id="linkedin-informations">Token here!</p>

<button id="linkedin-logout" type="button">Logout</button>
<p id="linkedin-results">Results here!</p>

<script>
  $('#linkedin-login').on('click', function(e){
    // e.preventDefault()
    var url_popup = ""
    var ltoken = ""
    var lurl = ""
    $.post('/auth/linkedin/url', function(json) {
      console.log(json)
      url_popup = json.link
      if (url_popup != "") {
        console.log('Successful to load url popup!')
        const w = 600
        const h = 600
        const top = (screen.height - h) / 4, left = (screen.width - w) / 2
        
        var child = window.open(url_popup, "popupWindow", `width=${w}, height=${h}, top=${top}, left=${left}, scrollbars=yes`)
        
        function checkChild() {
          if (child.closed) {  
            clearInterval(timer);
            $.post('/auth/linkedin/token', function(json) {
              console.log('Load token link successful!')
              $('#linkedin-informations').html('Token is comming ...')
              ltoken = json.token
              console.log(json.token)
              $('#linkedin-informations').html(json.token)
            })
            $.post('/auth/linkedin/info', function(json) {
              console.log('Load info link successful!')
              $('#linkedin-results').html('Information is comming ...')
              console.log(json)
              $('#linkedin-results').html(`Your login account: ${json.name}`)
              
            }) 
          }
        }

        var timer = setInterval(checkChild, 500);
      }
    })
    
    
  })

  $('#linkedin-logout').on('click', function(e){
    e.preventDefault()
    $.post('/auth/linkedin/out', function(json) {
      console.log('Log out successful!')
      $('#linkedin-results').html(`You logged out!`)
    })
  })

</script>

Successful screen: enter image description here