【问题标题】:Rails Azure Active Directory SSO with Web + iOS App Authentication带有 Web + iOS 应用身份验证的 Rails Azure Active Directory SSO
【发布时间】:2018-04-28 19:06:49
【问题描述】:

我需要让用户从 iOS 应用程序登录到他们的 Azure Active Directory 帐户,并在获取令牌后将信息传递到我的 rails 服务器,然后该服务器将对用户进行身份验证并创建他们的本地 (Rails) 用户记录如果认证成功。或者,对于现有用户,它会(理想情况下)设置 Devise current_user 变量,以便后续对 Rails 应用程序资源的请求会成功。

这个等式的两边都独立工作——iOS/Swift 4 应用程序使用 Microsoft SDK 来获取令牌——它工作正常。 Rails 端设置了 Azure Active Directory Omniauth 提供程序,当您通过浏览器访问它时它可以正常工作。到目前为止,一切都很好。 (注意:两者都使用 Azure AD v1.0 API:iOS 上的 ADAL 和 Rails 上的 omniauth-azure-activedirectory)

当我过去与 Omniauth 合作时——特别是使用 Facebook 提供程序时,我可以在应用程序端获取令牌并将其传递给 Rails 服务器,然后该服务器将使用 Facebook 图表来确保令牌通过简单的 GET 请求是合法的。但是,使用 Microsoft Omniauth 提供程序,似乎没有等效项。我发现令牌和声明在两者之间匹配,但是我不知道向 Rails 指示用户是合法的并且应该在身份验证详细信息匹配时在 Devise 中实例化为 current_user 的最佳方法。换句话说,我应该将哪些字段发送到 rails 端,然后我可以将其传递给 Azure AD 以验证用户的真实性?

我应该尝试使用 Omniauth 提供程序并将其返回基于 Web 的版本提供的内容(例如,omniauth.auth 哈希)还是应该编写自己的控制器/动作来接收来自应用程序的身份验证详细信息并实例化用户在从 Rails 再次验证用户之后我自己?我已经开始采用这两种方法,但到目前为止没有任何效果。

我在这两种方法中是否走在正确的道路上,还是有任何其他建议?

更新

我现在已切换到使用 v2.0 API,并且我设置了一个新应用程序(在 apps.dev.microsoft.com),它在应用程序端使用 MSAL,在 Rails 端使用 microsoft_v2_auth .一旦我从应用程序端的 MSAL 获得访问令牌,我将它发送到我的 Rails 服务器中的 POST 正文(通过 HTTPS)。服务器端,我只是使用从应用程序获得的令牌作为授权标头中的不记名令牌,在单独的操作(与 Omniauth 提供程序无关)中针对 Graph API 'me' 端点执行 GET。如果返回时没有出现错误,我会在用户不存在时创建用户,然后将其登录,或者如果用户确实存在,则直接登录。这似乎有效。

我只是使用 Omniauth 提供程序进行网络登录。

所以!这种方法看起来合理吗?除了对用户进行身份验证外,我真的不需要 Azure AD 中的任何东西。使用我从应用程序获得的令牌来自 Graph API 的 GET 请求似乎是最简单的方法?还有其他想法或顾虑吗?

【问题讨论】:

  • 我没有真正与 RoR 合作过,所以我无法给出正确的答案。但似乎您的 RoR 应用程序基本上是一个 API。如果是这种情况,则应该无法使用浏览器对其进行身份验证。您应该将 API 定义为 Azure AD 中的单独应用,并且您的 iOS 应用应获取该 API 的访问令牌。那么你的 RoR 应用需要实现 JWT 不记名令牌认证。
  • 令牌身份验证的工作方式是从 Azure AD 上的知名端点下载配置清单,然后检查令牌是否使用其中定义的密钥之一进行签名(并且签名正确,颁发者和观众是预期的等等。)这篇文章有一个小例子,虽然我更喜欢使用现成的库而不是滚动你自己的验证逻辑:blog.craig.io/…
  • @juunas 感谢您的反馈。不幸的是,它不仅仅是一个 API。用户需要能够通过网络和应用程序登录。不过,您对 JWT 不记名令牌的评论的第二部分是我现在正在采用的方法。再次感谢!
  • 是的,这些并不罕见。在这种情况下,您的应用基本上需要支持两种身份验证方法:一种用于浏览器客户端的基于 cookie 的方法,一种用于服务客户端的基于承载令牌的方法。
  • 这与我的想法有点不同,但我也没有看到你的方法有什么明显的错误。从某种意义上说,如果令牌包含您需要的信息,您甚至可能不需要调用 Graph API。尽管如此,您确实需要确保正确进行令牌验证。

标签: ios ruby-on-rails ruby swift azure


【解决方案1】:

由于有人要求我对此进行更深入的了解,因此我将继续发布我最终实施的解决方案。

将此添加到您的 gemfile:

gem 'omniauth-microsoft_v2_auth'

将此添加到 config/devise.rb 文件的底部:

config.omniauth :microsoft_v2_auth, ENV['CLIENT_ID'], ENV['CLIENT_SECRET']

您必须将这些环境变量添加到您的环境中。我通常在开发时使用 Figaro gem,然后在部署时通过 Heroku CLI 设置它们。

您需要通过会话控制器将用户路由到您的 Azure 身份验证。将此添加到您的 routes.rb 文件中:

devise_for :users, :controllers => {sessions: 'sessions', registrations: 'registrations', omniauth_callbacks: "callbacks"} 

devise_scope :user do
  post 'users/sign_in_with_azad_token' => 'sessions#sign_in_with_azad_token'
end

以下是我在会话控制器中实现操作的方式:

def sign_in_with_azad_token
    token = params[:token]

    # Use the graph API to ensure this user is legit
    uri = URI("https://graph.microsoft.com/v1.0/me/")
    request = Net::HTTP::Get.new(uri.request_uri)
    request['authorization'] = "Bearer #{token}"

    response = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
        http.request(request)
    end

    json = JSON.parse(response.body)

    if json["error"].nil?
        # If no error, user was found
        user = User.from_uid_email(json["id"], json["userPrincipalName"])
        unless user.nil?
            sign_in("user", user)
            respond_to do |format|  
                format.json {  return render :json => {  :success => true, :user => user } }  
            end
        else
            respond_to do |format|  
                format.json {  return render :json => {  :success => false, :message => "You must sign in before continuing" }, status: :unauthorized }  
            end
        end

    else
        respond_to do |format|  
            format.json {  return render :json => {  :success => false, :message => json["error"]["message"]}, status: :unauthorized }  
        end
    end

end

您可能希望有一种方法允许您的用户在 webi 浏览器中使用 Microsoft Azure 进行身份验证。在这种情况下,您需要将其添加到您的设计新注册视图(我的位于views/devise/registrations/new.html.erb 和views/devise/shared/_links.html.erb):

    <%- if devise_mapping.omniauthable? %>
      <%- resource_class.omniauth_providers.each do |provider| %>
        <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %><br />
      <% end -%>
    <% end -%>

最后,我的客户端应用程序执行登录的端点位于 ...[server_url]/users/sign_in_with_azad_token.json

在应用程序端使用 MSAL iOS 框架进行身份验证并获得令牌后,我点击了此端点。我将令牌包含在作为请求正文一部分的 JSON 有效负载中。有效载荷很简单:

{
    "token" : "dj9hs0shgkshd93hs92bh7493"

}

希望这会有所帮助。自从我从事此工作以来已经有一段时间了,所以我可能忘记了一些事情。如果似乎缺少任何内容,请在此答案中添加评论。

【讨论】:

  • 非常感谢,Matt,这是一个非常全面的答案!我前面有一个非常相似的设置:iOS 应用程序、Rails API、Web 客户端安装在同一个机架上。我认为会有 2 个选项: 1. 您选择的选项:iOS 应用程序获取 access_token,将其传递给 API,API 在 GraphAPI 上验证它 2. 应用程序获取 id_token,将其传递给 API,API 验证 JWT 令牌使用与omniauth 提供者类似的方法,API 返回自己生成的access_token,而不是Graph API。你考虑过后者吗?你有什么理由不去?
  • @dg6 简单的答案是,一旦我找到了一个似乎可行的解决方案,我就没有再看太远了。您的第二种选择/解决方案是否有其他好处?
【解决方案2】:

看看https://github.com/joshsoftware/sso-devise-omniauth-client 的想法。 Devise 支持 SSO,但您需要设置一些控制器,并且可能需要设置自定义 OmniAuth 策略。

也可以结帐http://codetheory.in/rails-devise-omniauth-sso/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-05
    • 1970-01-01
    • 2020-11-26
    • 1970-01-01
    • 1970-01-01
    • 2016-06-02
    相关资源
    最近更新 更多