如何在Rails API中使用Omniauth和Facebook进行用户认证?

56

我正在构建一个Rails API,并成功建立了一种用户可以使用Omniauth Identity进行身份验证的方法。

我们只需从客户端发布到auth/identity/callback,传递一个auth_key和密码。
然后服务器返回一个doorkeeper令牌,用户从此使用该令牌访问应用程序并标识自己。

下图说明了这一点:

客户端服务器关系

现在我们想要实现从客户端进行Facebook登录,但从理论上和实际上都遇到了问题。

在一个简单的Omniauth Identity Rails应用程序中,您只需要调用auth/facebook,但如果我们在客户端放置一个链接,它会调用服务器,然后服务器记录:

INFO -- omniauth: (facebook) Request phase initiated.

在Facebook中正确设置了应用程序的ID和Secret,因此可能是登录提示返回到服务器了吗?

我正在混淆身份验证链接。非常感谢任何帮助!

输入图像描述


你看过 omniauth-facebook gem 吗?https://github.com/mkdynamic/omniauth-facebook - CDub
我不确定是否理解您的问题,但oauth链如下:客户端->您的服务器->Facebook服务器->客户端->Facebook服务器->您的服务器。您的API是如何实现的?客户端是否有Web界面? - Ashitaka
是的 - 客户端是一个Angular应用程序,服务器是Rails API。如果身份验证在我的服务器上进行,我知道我可以很容易地完成它,但我无法理解它。理想情况下,用户访问客户端,点击“通过Facebook登录”,然后使用Facebook验证其凭据,然后我的服务器响应一个令牌供他们浏览受保护的API。 - idrysdale
1
是的,那将会是更简单的流程,但这不是Omniauth的工作方式。也许你不应该使用Omniauth,而是看看Facebook的客户端身份验证。或者尝试将两者结合起来。你应该查看这个railscast: http://railscasts.com/episodes/360-facebook-authentication - Ashitaka
5
你最后在这里做了什么?我也在想同样的事情... - Brandon
显示剩余3条评论
3个回答

60

我发现最好的方法(在遇到这个问题一段时间后)是手动进行Omniauth2认证(特别是在我的情况下使用satellizerangular插件)...

我将讨论Facebook的解决方案,因为这是我的情况,但所有内容都适用于任何其他提供商。

首先,您需要了解Omniauth2的工作方式(如此文档化...)

  1. 客户端:打开弹出窗口以供用户进行身份验证。
  2. 客户端:登录(如果必要),然后授权应用程序。
  3. 客户端:成功授权后,弹出窗口将重定向回您的应用程序,并带有code (授权代码)查询字符串参数

重定向返回的URL必须与您的前端应用程序URL匹配,而不是后端URL,并且必须在您的Facebook应用程序配置中指定

4. 客户端:code参数发送回打开弹出窗口的父窗口。 5. 客户端:父窗口关闭弹出窗口,并向backend/auth/facebook发送POST请求,其中包含code参数。 6. 服务器:code授权码)被交换为访问令牌

这里详细描述了如何从Facebook开发者文档中用code交换access-token

  1. 服务器:使用在步骤6中检索到的access-token来检索用户信息。

  2. 完成了,您已经拥有一个可以合并/创建帐户/与其他oauth提供商链接等操作的用户。但请注意,用户可能会撤销某些权限(例如电子邮件,Facebook支持撤销某些权限)...


首先,您需要将HTTParty gem添加到您的Gemfile中。
gem 'httparty'  # Makes http fun again (http client)

我已经添加了这个代码片段,其中包含第6、7和8步的流程,这些步骤是最棘手的,并且几乎没有文档记录。
这个代码片段导出了2个主要方法:
Omniauth::Facebook.authenticate(authorization_code)

此功能用于通过 Facebook 认证用户并返回用户信息、长期访问令牌(有效期为 60 天)

Omniauth::Facebook.deauthorize(access_token)

这是用于在Facebook上取消授权/撤销access_token和应用程序权限的特殊需求...

当用户在Facebook登录时撤销了请求的电子邮件权限时,我们会撤销整个应用程序的权限...这将在下次登录时提示用户,就像这是他的第一次登录一样(无需转到Facebook应用程序并手动撤销应用程序)...

以下是控制器中的使用方法

user_info, access_token = Omniauth::Facebook.authenticate(params['code'])
if user_info['email'].blank?
  Omniauth::Facebook.deauthorize(access_token)
end

就是这样...如果你对实现的内部感兴趣...这里是在gist中看到的代码。(供参考添加) 请随意fork它,编辑它,帮助使它变得更好。

require 'httparty'

module Omniauth
  class Facebook
    include HTTParty

    # The base uri for facebook graph API
    base_uri 'https://graph.facebook.com/v2.3'

    # Used to authenticate app with facebook user
    # Usage
    #   Omniauth::Facebook.authenticate('authorization_code')
    # Flow
    #   Retrieve access_token from authorization_code
    #   Retrieve User_Info hash from access_token
    def self.authenticate(code)
      provider = self.new
      access_token = provider.get_access_token(code)
      user_info    = provider.get_user_profile(access_token)
      return user_info, access_token
    end

    # Used to revoke the application permissions and login if a user
    # revoked some of the mandatory permissions required by the application
    # like the email
    # Usage
    #    Omniauth::Facebook.deauthorize(access_token)
    # Flow
    #   Send DELETE /me/permissions?access_token=XXX
    def self.deauthorize(access_token)
      options  = { query: { access_token: access_token } }
      response = self.delete('/me/permissions', options)

      # Something went wrong most propably beacuse of the connection.
      unless response.success?
        Rails.logger.error 'Omniauth::Facebook.deauthorize Failed'
        fail Omniauth::ResponseError, 'errors.auth.facebook.deauthorization'
      end
      response.parsed_response
    end

    def get_access_token(code)
      response = self.class.get('/oauth/access_token', query(code))

      # Something went wrong either wrong configuration or connection
      unless response.success?
        Rails.logger.error 'Omniauth::Facebook.get_access_token Failed'
        fail Omniauth::ResponseError, 'errors.auth.facebook.access_token'
      end
      response.parsed_response['access_token']
    end

    def get_user_profile(access_token)
      options = { query: { access_token: access_token } }
      response = self.class.get('/me', options)

      # Something went wrong most propably beacuse of the connection.
      unless response.success?
        Rails.logger.error 'Omniauth::Facebook.get_user_profile Failed'
        fail Omniauth::ResponseError, 'errors.auth.facebook.user_profile'
      end
      response.parsed_response
    end


    private

    # access_token required params
    # https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.3#confirm
    def query(code)
      {
        query: {
          # The authorization_code we want to exchange for the access_token
          code: code,
          # This must match the redirectUrl registerd in the facebook app.
          # You can save it to ENV['WEB_APP_URL'] if you have multiple facebook apps for development and testing
          # so you can support testing app on development and production app on production env.
          redirect_uri: "http://localhost:9000/",
          client_id: ENV['FB_APP_ID'], # Facebook appId
          client_secret: ENV['FB_APP_SECRET'], # Facebook app secret (must not exist on front-end app for security)
        }
      }
    end
  end
end

这里有另一篇Node.js教程,演示如何为Instagram实现OAuth,帮助我理解了OAuth2的工作原理(仅供参考)


2
我已经寻找类似的东西好几个小时了,谢谢! - Jeriko
我也遇到了同样的问题。只是为了明确:你实现的内容根本没有使用omniauth-facebook和omniauth宝石吗?或者他们的任何功能都是必需的? - Augusto Samamé Barrientos
不需要任何其他东西,只需要 'http-party' gem。 - a14m
好的。所以我的API会获取代码并使用您的方法从FB获取用户数据(包括FB uid和访问令牌)。到目前为止,这很有意义。但是一旦我拥有了这些数据,我是否需要手动登录/在我的用户模型中创建用户?只使用uid / access_token而不是通常的电子邮件/密码对吗?我正在使用Devise,通常Omniauth会处理此操作。在我的用户模型中只登录/创建一个用户是否可以?我得到了CODE,这让我假设这是一个经过身份验证的用户?我开始在我的脑海中理清流程,但希望确认我走在正确的道路上。 - Augusto Samamé Barrientos
1
我已经修复了到gist的链接(谢谢)...是作为一个独立的类实现的(而不是作为omniauth策略)...它实际上与omniauth gem无关(只是将类命名为这样以便在我的内部实现中工作...只是这样命名空间...你可以更改它)。但它确实依赖于“httparty”(但你可以使用标准库实现它而不需要任何依赖)。 - a14m
显示剩余2条评论

1
为了解决这个问题,我发现最好的资源是 satellizer github 仓库中的 rails 示例应用程序: https://github.com/sahat/satellizer/tree/master/examples/server/ruby 你的 satellizer 代码调用 AuthController.authenticate 方法。此方法使用 每个提供者的 oauth 模型类 将您收到的代码转换为访问令牌。然后在您的 user class 中,您可以检索与提供者提供的信息匹配的用户。
最后,控制器方法将 jwt 令牌返回给客户端。
在我的情况下,控制器部分有点不同,因为我还使用 devise 进行邮件/密码身份验证,但我按原样复制了 oauth 类,它运行得很好。

-1

8
这对于一个纯应用程序有效(我过去使用过它,这是一个很好的 Gem)。但从我所看到的来看,除非我漏掉了什么?否则无法用于 API。请参阅我几个月前的评论。 - idrysdale
5
请详细说明如何将OmniAuth宝石与基于API的应用程序集成。 - Evgenia Karunus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接