0

So far my website has been using fullstack views generated directly by Rails, and for linkedin authentication I used a redirect_uri routed to a Devise::OmniauthCallbacksController to handle the logic.

GET https://www.linkedin.com/oauth/v2/authorization?redirect_uri=my_devise_omniauth_callback_url

I am wondering how to migrate this to a standalone React frontend + a standalone Rails server. The React app should "spawn" a linkedin popup for authorization, and wait until all the authorization process is done before closing itself and proceeding with registration on the main window.

I have been through the Linkedin OAuth guide and I see the way from approving the connection to getting user info is quite long. I am happy that Devise/Omniauth does most of the job for me server-side and I'd rather keep it this way, instead of coding that logic in the frontend.

(By the way, is it safe to assume the Linkedin OAuth flow be similar to the Google one mentionned in this answer ?)

I am unsure about whether this can be done the way I see it on the React app (especially the communication of data between the linkedin popup window and the main react view). Also from what I understand the omniauth gem in rails expects to receive the one-time token from the OAuth provider before it can query the provider twice more (first time to exchange the access token, second time to get user info). Would the following work ?

  • Assume my React Client app is hosted on client.example.com
  • Assume my server api is hosted on api.example.com
  • The React app opens a popup windows, that is used to go to the Linkedin auth URI with
    • (1) A redirect uri that would point directly to api.example.com
    • (2) A redirect_uri on client.example.com, and my React client would then have to forward the token to api.example.com
  • The authorization code somewhat reaches my server api.example.com, which gets an access code and retrieves user data from Linkedin. It returns to the browser an identity_id which can be used for the registration
  • The identity_id is actually returned to the popup window, and transferred back to the main React app (how ?)
Cyril Duchon-Doris
  • 12,964
  • 9
  • 77
  • 164

1 Answers1

2

I had the same problem and I found an hacky but interesting solution :-)
(I use all the OAuth logic from devise and omniauth-linkedin gems.)

In my application, you can start to fill up a form and you need to login before save it. I needed a way to log the user without loosing form data and without reloading the page.

When the user visit the page, he is given a unique uuid to identify him. This uuid is used to start a websocket connection so I can push data directly to this tab later.

I open the LinkedIn OAuth in a new tab with javascript window.open() so I can close this tab with window.close().

You can pass additional parameters to the OAuth authentication, I pass the unique uuid so I can recover it when the authentication is a success and with this uuid I can notify the first tab by sending a message via websocket (In my application I send the user infos to update state with current user).

After authentication, I redirect the user on a page containing only window.close().

enter image description here

Setup LinkedIn OAuth in Rails

You will need a functional Omniauth/Devise authentication working.

omniauth_callback_controller.rb

class OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def linkedin
    user = User.connect_to_linkedin(request.env["omniauth.auth"], current_user)
    guest_guid = request.env["omniauth.params"]["guestGuid"]

    if user.persisted?
      ActionCable.server.broadcast("guest:#{guest_guid}", { user: UserSerializer.new(user).to_h })
      sign_in(user)
      redirect_to landing_home_index_path
    end   
  end
end

user.rb

def self.connect_to_linkedin(auth, signed_in_resource = nil)
  user = User.where(provider: auth.provider, linkedin_uid: auth.uid).first

  if user
    return user
  else
    registered_user = User.where(email: auth.info.email).first

    if registered_user
      return registered_user
    else
      user = User.create(lastname: auth.info.last_name, firstname: auth.info.first_name,
        provider: auth.provider, linkedin_uid: auth.uid, email: auth.info.email,
        linkedin_token: auth.credentials.token, linkedin_secret: auth.credentials.secret,
        linkedin_picture: auth.extra.raw_info.pictureUrl, password: Devise.friendly_token[0, 20])
    end
  end
end

routes.rb

  devise_for :users, controllers: {
    omniauth_callbacks: "omniauth_callbacks",
    sessions: "users/sessions"
  }

Now if you visit /users/auth/linkedin you will be able to log/create user with LinkedIn OAuth.

Websocket connection

create_job_offer_channel.rb

class CreateJobOfferChannel < ApplicationCable::Channel
  def subscribed
    stream_from "guest:#{params[:guest]}"
  end

  def unsubscribed
  end

  def receive(data)
    ActionCable.server.broadcast("guest:#{params[:guest]}", data)
  end
end

create_job_offer.js.coffee

class window.CreateJobOffer
  constructor: (params) ->
    self = @

    @channel = App.cable.subscriptions.create {
      channel: "CreateJobOfferChannel", guest: params["guest"]
    }, self

    @onReceive = params["onReceive"] || null


  received: (data) =>
    @onReceive(data) if @onReceive

React frontend

Link to LinkedIn OAuth (with devise and omniauth)

<div className="btn-linked-in">
  <i className="fa fa-linkedin-square"></i>
  <span onClick={() => open(`/users/auth/linkedin?guestGuid=${guestGuid}`)}>
    Login with LinkedIn
  </span>
</div>

Hope this help :)

Aschen
  • 1,691
  • 11
  • 15
  • Hey that sounds like something cool. I'm just wondering one thing : is it not possible to have the linkedin callback point to the a React URL instead, and then the frontend would just "forward" this information to the server's omniauth callback controller ? This way we might not even need websockets... (in the Google diagram I linked, these are steps 3) and 4) ) – Cyril Duchon-Doris Jun 12 '17 at 08:13
  • 2
    You don't need to use websocket if you are not concern about keeping the user on the exact same page with the same informations when the OAuth connection is ok. If you just want to connect the user, you can open the OAuth authentication in the current tab and after success redirect on the react frontend. – Aschen Jun 12 '17 at 08:17
  • Thanks, I think for now I will try to keep things simple without websockets (as I don't even have action_cable setup yet and the same page form authentication is a compromise I can make) but this is definitely something that I will want in the future when I have time to setup everything. – Cyril Duchon-Doris Jun 12 '17 at 08:22