使用 Apple 登录

@David, the author of the PR has responded to my comment, what’s your opinion on this?:

Me:

This PR appears to solve the issue with the lack of name and email?

PR Author:

No, unfortunately not. Apple needs to provide a REST API endpoint to retrieve name/email as they currently only pass this information upon successful authentication once and not on subsequent authentications.

Isn’t one-time at initial Authorisation good enough for us?

It’s better than nothing, but it’s still not good. For example, if you “sign in with apple”, but then click cancel on the “create account” screen. Or if you connect an existing account with apple, then decide to create a new account instead. Hopefully Apple will resolve that before the end of the beta :crossed_fingers:

5 个赞

目前是否有“使用 Apple 登录”功能的可用实现?我为客户的电商平台开发的项目计划推出 iOS 应用,但若没有此选项,我们就无法启用其他身份验证方式,否则应用可能因违反 App Store 审核指南而被拒绝。

据我所知没有。我们正在等待这个问题得到解决 https://github.com/nhosoya/omniauth-apple/issues/8,不过仓库所有者不恰当地关闭了它。

我不太确定,但像 这个提交 以及 八月的其他提交 能解决这个问题吗?

请随意安装插件进行测试,但我尚不清楚该问题是否已解决。不过,在开发完成或至少由开发人员确认之前,我不建议在正式环境中使用。

我强烈支持这项功能。我希望它能像其他登录选项一样,直接内置到原生 Discourse 中。

我认为这是故意无法检索此类信息的。

在您的应用或网站中显示“使用 Apple 登录”按钮,意味着用户只需轻点一下,即可使用他们已有的 Apple ID 登录或注册,无需填写表单、验证电子邮件地址或设置密码。“使用 Apple 登录”提供了一种全新的、更私密的方式,让用户能够简单快速地登录应用和网站,同时提供他们可信赖的一致登录体验,并免去记忆多个账户和密码的麻烦。在您需要请求姓名和电子邮件地址的情况下,用户可以选择隐藏其真实电子邮件地址,转而分享一个唯一的随机电子邮件地址。

查看 Apple 的完整开发者文章

你说得对,隐私确实是“使用 Apple 登录”的核心部分之一,但你引用的关键部分是:

假设用户选择向我们提供姓名和电子邮件,那么我们期望每次用户登录时都能从提供商处收到这些信息。但在当前实现中,情况并非如此。首次认证后,我们再也无法获取用户信息。

我认为这并非该 gem 的作者能够修复的问题——这需要 Apple 进行更改。我看不到这种情况短期内会发生,因此我们或许只能要求用户在 Discourse 中手动输入他们的姓名和电子邮件 :cry:

4 个赞

是的,但对于那些最终选择不提供信息的用户该怎么办呢?

哦,这里有一个粗略的概念图。:sweat_smile:

好消息:

我部分让 Robert/merefield 的插件的分支版本运行起来了(该分支仅涉及切换到我从 GitHub 最新源码构建的 omniauth gem 副本)。不过,在我的测试 Discourse 实例上(通过 ngrok 实现了端到端 HTTPS),我必须将“站点 Cookie”站点设置设为“(无)”,身份验证才能正常工作。禁用该设置后,我可以创建账户(即使关闭了注册表单),也能重新登录;但如果启用该设置,则无法做到。

以下是登录失败的回溯信息:

登录失败回溯
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/logster-2.5.1/lib/logster/logger.rb:112:in `report_to_store'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/logster-2.5.1/lib/logster/logger.rb:103:in `add_with_opts'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/logster-2.5.1/lib/logster/logger.rb:54:in `add'
/usr/local/lib/ruby/2.6.0/logger.rb:543:in `error'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:163:in `log'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:486:in `fail!'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-oauth2-1.6.0/lib/omniauth/strategies/oauth2.rb:71:in `callback_phase'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:238:in `callback_call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:189:in `call!'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:169:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:192:in `call!'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:169:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:192:in `call!'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:169:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:192:in `call!'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:169:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:192:in `call!'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:169:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:192:in `call!'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/strategy.rb:169:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/omniauth-1.9.0/lib/omniauth/builder.rb:64:in `call'
/var/www/discourse/lib/middleware/omniauth_bypass_middleware.rb:47:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/tempfile_reaper.rb:15:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/conditional_get.rb:38:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/head.rb:12:in `call'
/var/www/discourse/lib/content_security_policy/middleware.rb:12:in `call'
/var/www/discourse/lib/middleware/anonymous_cache.rb:318:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/session/abstract/id.rb:259:in `context'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/session/abstract/id.rb:253:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/cookies.rb:648:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.1/lib/active_support/callbacks.rb:101:in `run_callbacks'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/actionable_exceptions.rb:17:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/debug_exceptions.rb:32:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/logster-2.5.1/lib/logster/middleware/reporter.rb:43:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-6.0.1/lib/rails/rack/logger.rb:38:in `call_app'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-6.0.1/lib/rails/rack/logger.rb:28:in `call'
/var/www/discourse/config/initializers/100-quiet_logger.rb:18:in `call'
/var/www/discourse/config/initializers/100-silence_logger.rb:31:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/request_id.rb:27:in `call'
/var/www/discourse/lib/middleware/enforce_hostname.rb:17:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/method_override.rb:22:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/executor.rb:14:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/sendfile.rb:111:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-6.0.1/lib/action_dispatch/middleware/host_authorization.rb:77:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-1.1.4/lib/mini_profiler/profiler.rb:184:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/message_bus-2.2.3/lib/message_bus/rack/middleware.rb:57:in `call'
/var/www/discourse/lib/middleware/request_tracker.rb:181:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-6.0.1/lib/rails/engine.rb:526:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-6.0.1/lib/rails/railtie.rb:190:in `public_send'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-6.0.1/lib/rails/railtie.rb:190:in `method_missing'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/urlmap.rb:68:in `block in call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/urlmap.rb:53:in `each'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.8/lib/rack/urlmap.rb:53:in `call'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.2/lib/unicorn/http_server.rb:605:in `process_client'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.2/lib/unicorn/http_server.rb:700:in `worker_loop'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.2/lib/unicorn/http_server.rb:548:in `spawn_missing_workers'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.2/lib/unicorn/http_server.rb:144:in `start'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.2/bin/unicorn:128:in `<top (required)>'
/var/www/discourse/vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `load'
/var/www/discourse/vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `<main>'

有人有什么建议吗?我该如何重写该插件,使其核心功能不再依赖禁用此站点设置?我的插件代码位于 https://github.com/sau226dev/discourse-sign-in-with-apple,用于重新构建 omniauth gem 的最新代码应位于同一 GitHub 组织中。

提前感谢大家提供的任何帮助,

sau226

4 个赞

该插件最初在一定程度上可以工作,但由于上述 David 描述的问题,近期并未得到关注。

在我们收到苹果已解决这一根本性障碍(即每次登录尝试时发送姓名和电子邮件)的积极消息之前,依我看,维护这段代码并不值得。我不认为您有什么办法可以绕过它?这也是我甚至没有尝试更新依赖项的原因。无论如何,该插件在测试中都会失败。

因此,这并非一个“已发布”的插件(否则此类或类似话题会出现在 #plugin 频道中),在问题解决之前,它不太可能获得任何支持。如果该问题得到解决,且苹果能够提供相关信息,我将非常乐意将其完善。

顺便提一下,还存在另一个严重问题:要将其用于自己的网站,您需要付费加入苹果开发者计划,以获取在苹果系统上进行设置的权限。我怀疑这会让许多预算有限的网站望而却步,因为注册并非免费或低价。

6 个赞

我认为 @sau226 似乎暗示未返回电子邮件/姓名实际上并不是一个阻碍?

@orenwolf 后续登录时未返回邮箱/姓名这一情况似乎并未构成问题。我认为我能够关闭注册窗口,并重新使用正确的信息进行注册。正如我之前所说,我随后可以立即使用 Apple 账号登录,没有任何问题。

我遇到的唯一问题是 CSRF 错误,除非禁用我之前提到的站点设置。一个潜在的问题是注册表单中缺少姓名输入,且用户名默认为邮箱中 @ 符号前的部分(不过在我看来,这些潜在问题要么根本不算问题,要么用户可以轻松解决)。

4 个赞

除了大卫上面的评论外,我在苹果开发者支持网站上发现了一个相关主题,该主题获得了官方回复并确认了该问题:

官方回复:

您好,aslkdjalksdjasdasd,
此行为符合预期。用户信息仅在首次用户注册时通过 ASAuthorizationAppleIDCredential 发送。之后使用“通过 Apple 登录”以同一账户登录您的应用时,不会共享任何用户信息,ASAuthorizationAppleIDCredential 中仅会返回用户标识符。建议您安全地缓存包含用户信息的初始 ASAuthorizationAppleIDCredential,直到您能在服务器上验证账户已成功创建。
Patrick

正如一位开发者评论的那样:

等等……如果由于某些原因,来自苹果的首次重定向因多种非常常见的原因而丢失,那么我们就永久性地失去了该用户,因为没有任何其他方式可以获取他们的信息。真的没有其他方法可以获取这些信息吗?

另一位开发者则评论道:

或者,如果下游出现问题,客户会投诉,而支持人员会让他们前往 Apple ID 网站撤销权限,以便重新正确注册。我认为这将带来糟糕的体验,并导致人们在遇到此类问题时不再使用此登录机制。

因此,遗憾的是,我认为在生产环境中无法安全地使用此功能。这将会成为支持方面的噩梦。

我建议暂时搁置此问题,直到苹果意识到他们所制造的问题:在试图提高安全性的过程中,他们似乎过度牺牲了系统的健壮性。

11 个赞

真让人失望。:cry:

1 个赞

Apple 已更新其“使用 Apple 登录”开发者页面,增加了关于数据收集、数据管理等方面的更多信息。

5 个赞

我已提交一个 拉取请求,用于将 omniauth-apple gem 更新至最新版本。该版本包含 此提交,似乎可能解决每次登录时无法获取用户邮箱的问题。

为了尝试此方案,我按照 推荐的博文 配置了凭据,但截至目前仍有两点未能弄清楚:

  • 设置 Apple 服务 ID 所需的返回 URL 重定向路径是什么?
  • 如何获取“验证 txt 文件”,或者是否不再需要?我在搜索中发现有提及该文件可从 Apple 下载,作为域名/邮箱通信设置的一部分,但这似乎已不再适用:
3 个赞

谢谢。

通常,你在成功测试某项功能后提交 PR 即可 :wink:

请在完成后确认。

这看起来似乎显而易见,但如果我们能收到苹果方面的声明,确认他们现在每次都会发送邮件,我会更有信心。如果苹果没有解决核心问题,仅仅更改 gem 版本是无法解决这一情况的。

等我重新订阅苹果开发者计划时(我目前尚未完成),我会检查一下我的设置……(说实话,这次事件确实让人有些泄气)

@David

我已经尝试了 升级我们插件的分支 以使用最新的 omniauth-apple。(注意:除了升级版本号外,还需要进行其他一些更改)。

tl;dr:问题依然存在

我成功地在沙盒环境中通过一些“黑客”手段使其运行,但它仍然存在一些问题:

  1. Apple 在回调时使用 POST 请求。这在 OAuth 实现中并不常见,因为这意味着带有 samesite=Lax 属性的 Cookie 将不会随请求发送。这导致 Discourse 在回调期间无法读取会话 Cookie,从而引发 CSRF 错误。

    不安全的临时解决方案是将 Discourse 的 Cookie 设置为 samesite=None 来禁用此安全机制。

  2. 在没有 CSRF 令牌的情况下使用 POST 请求还会触发核心中的另一项安全措施。

    不安全的临时解决方案是删除 这一行

  3. 当 omniauth gem 获取 JWKs 时,我收到了来自 Apple 的 403 错误。我怀疑 Accept: 头未正确设置,但尚未验证这一点。

    不安全的临时解决方案是硬编码密钥。

经过所有这些操作后,我终于成功使用 Apple 登录了。您可以在 https://sandbox.dtaylor.uk 上尝试(我会让它保持运行几天,但请勿在其中输入任何敏感信息,因为它是不安全的)。

然后……电子邮件和姓名仍然只在首次认证时包含。您可以尝试一下:使用 Apple 登录,取消账户创建,然后再次尝试。第二次尝试将缺少您的详细信息。

因此,假设 Apple 短期内不会改变现状……我们该如何解决这个问题?

对于问题 (1) 和 (2),我认为可以将 Apple 的 POST 请求转换为 GET 请求,而不会影响安全性。当我们在回调中收到 POST 请求时,可以渲染一些 JavaScript 代码,将 window.location 设置为 /auth/apple/callback?code=...&state=...。此后,它将像其他任何提供商一样正常工作。不过,我认为拦截 POST 请求需要对核心 API 进行一些更改。

对于问题 (3),我认为通过一些在 omniauth gem 中的小改动可能就能解决。

但我们仍然无法获取姓名和电子邮件,所以我不确定是否值得修复这些其他问题 :cry:

8 个赞

感谢您再次尝试并提供分析!或许可以向苹果提交一个技术支持工单,以厘清剩余的问题?根据我的经验,在那里他们更有可能提供可行的解决方案或变通方法,而在苹果开发者论坛上则未必如此。