使用React Frontend + Rails API对Linkedin OAuth进行身份验证

时间:2017-06-07 10:37:25

标签: oauth devise linkedin ruby-on-rails-5 omniauth-linkedin

到目前为止,我的网站一直使用Rails直接生成的fullstack视图,对于linkedin身份验证,我使用redirect_uri路由到Devise::OmniauthCallbacksController来处理逻辑。

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

我想知道如何将其迁移到独立的React前端+独立的Rails服务器。 React应用程序应该"产生"一个用于授权的linkedin弹出窗口,等待所有授权过程完成后再关闭自己并继续在主窗口上注册。

我已经通过Linkedin OAuth guide,我看到从批准连接到获取用户信息的方式很长。我很高兴Devise / Omniauth为我服务器方面完成了大部分工作,我宁愿保持这种方式,而不是在前端编写逻辑。

(顺便说一下,假设Linkedin OAuth流程类似于the Google one mentionned in this answer是否安全?)

我不确定这是否可以按照我在React应用程序上看到的方式完成(特别是在LUN弹出窗口和主反应视图之间的数据通信)。另外根据我的理解,rails中的omniauth gem期望从OAuth提供者接收一次性令牌,然后再次向提供者查询两次(第一次交换访问令牌,第二次获取用户信息)。以下是否有效?

  • 假设我的React Client应用程序托管在client.example.com
  • 假设我的服务器api托管在api.example.com
  • React应用程序打开一个弹出窗口,用于转到Linkedin auth URI
    • (1)重定向uri,直接指向api.example.com
    • (2)cli​​ent.example.com上的redirect_uri,然后我的React客户端必须将令牌转发到api.example.com
  • 授权码有点到达我的服务器api.example.com,它获取访问代码并从Linkedin检索用户数据。它返回浏览器identity_id,可用于注册
  • identity_id实际上已返回弹出窗口,并转回主React应用程序(如何?)

1 个答案:

答案 0 :(得分:2)

我有同样的问题,我找到了一个hacky但有趣的解决方案:-)
(我使用deviseomniauth-linkedin宝石中的所有OAuth逻辑。)

在我的应用程序中,您可以开始填写表单,然后在保存之前需要登录。我需要一种方法来记录用户而不会丢失表单数据而无需重新加载页面。

当用户访问该页面时,他会获得一个独特的uuid来识别他。这个uuid用于启动websocket连接,因此我可以稍后将数据直接推送到此选项卡。

我使用javascript window.open()在新标签页中打开LinkedIn OAuth,因此我可以使用window.close()关闭此标签。

您可以将其他参数传递给OAuth身份验证,我传递了唯一的uuid,以便在身份验证成功时我可以恢复它并使用此uuid我可以通过websocket发送消息通知第一个选项卡(在我的应用程序中发送用户信息以使用当前用户更新状态。

身份验证后,我将用户重定向到仅包含window.close()的页面。

enter image description here

在Rails中设置LinkedIn OAuth

您需要有效的Omniauth / Devise身份验证工作。

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"
  }

现在,如果您访问/users/auth/linkedin,您将能够使用LinkedIn OAuth登录/创建用户。

Websocket连接

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

反应前端

链接到LinkedIn OAuth(使用设计和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>

希望这有帮助:)