【问题标题】:Refreshing token in Oauth2 Implicit Grant Flow and 3rd party cookies在 Oauth2 隐式授权流和第 3 方 cookie 中刷新令牌
【发布时间】:2019-07-15 07:03:42
【问题描述】:
我想知道如何处理 Oauth2 隐式授权中的刷新令牌
2019 年主流浏览器默认禁用第 3 方 cookie 时的流量。
一些细节:
当前设置:
场景:
当用户登录时,身份提供者为域 uaa.api.example.com 设置包含用户详细信息的 cookie,并在重定向的 Location 标头中返回 JWT。
JWT存储在本地存储中(ui.example.com),但有效期只有1小时,所以我想刷新一下。
可以通过发送到 IDP 授权端点的 prompt=none 查询参数进行刷新(过程在 Auth0 guide (it's not UAA but flow is the same 中有详细描述)
在每 20m 隐藏 iframe 中创建 src 设置为 uaa.api.exmaple.com/oauth/authorize?prompt=none 的内容,无需用户提供凭据即可启动登录过程。当流程结束时,响应中返回的新 JWT 会再次存储在本地存储中。
问题:
问题:
对于我的 3rd 方 cookie 问题有什么解决方案吗?
如果没有,我可以使用隐式授权流程和 SPA 的替代方法来登录和刷新令牌吗?
【问题讨论】:
标签:
oauth
oauth-2.0
architecture
jwt
cloudfoundry-uaa
【解决方案1】:
作为托管在不同域中的应用程序和身份服务器。这意味着您的应用程序正在执行跨域身份验证。跨域认证是通过第三方cookie实现的,禁用第三方cookie会导致跨域认证失败。
回答您的问题:
对于我的 3rd 方 cookie 问题有什么解决方案吗?
将您的应用程序和身份服务器托管在同一服务器下
领域。在这种情况下,您可以使用子域。
如果没有,我可以使用隐式授权流程和 SPA 的替代方法来登录和刷新令牌吗?
没有
解决方案:
我不熟悉 CloudFoundry。不确定他们是否支持。您可以通过在身份提供者端启用自定义域来解决此问题。因此,您的应用程序和身份提供者都将在同一个域中,并且 cookie 将被视为第一方。例如,将您的应用程序托管在https://acme.com 并将您的身份提供者自定义域设置为https://login.acme.com
【解决方案2】:
问题:
对于我的 3rd 方 cookie 问题有什么解决方案吗?
如果您在应用和 IDP 之间使用相同的顶级域,那么在禁用 3rd 方 cookie 时应该没有问题。
This link 还详细说明了使用跨域策略取得的成败参半。
如果没有,是否有隐式授权流程和 SPA 的替代方案
我可以用来登录和刷新令牌吗?
我以前没有使用过 CloudFoundry,但大多数大型 OAuth2.0 提供商都提供公共客户端功能,其中公共客户端(例如您的 SPA)不需要客户端密码来获取访问/刷新令牌。这允许公共客户端使用Authorisation Code Grant,这可以允许通过刷新令牌刷新令牌,从而避免使用 HTTP 重定向和 cookie 的 silent auth 技术。
【解决方案3】:
问题的根源在于iframe和隐式授权类型的使用。
我认为您使用 iframe 的原因是为了跨域访问 cookie。现在,避免使用iframe 的最简单方法是将cookie 的域设置为Domain=example.com,并在example.com 上同时拥有UI 和授权服务器。如果由于某种原因,您不能这样做,则需要采用以下方法。
推荐选项
隐式授权类型不安全。虽然这个问题不是关于授权类型的利弊,但为了给我要解释的选项设置背景,让我简单列举一下我说隐式流不安全的原因:
- 缺少通过提供客户端密码和授权码的客户端身份验证步骤。所以安全性较低
- 访问令牌作为 URL 片段发回(这样令牌不会发送到服务器),该片段将继续保留在浏览器历史记录中
- 如果发生 XSS 攻击,恶意脚本可以很好地将令牌发送到攻击者控制的远程服务器
因此,推荐的选项是使用授权码授权类型。在 SPA(单页应用程序)中不使用授权码的原因之一是,它要求将客户端密码存储在浏览器中,我们知道浏览器无法保密。通过在服务器端使用代理组件(可以嵌入到资源服务器中)来保存客户端机密并充当 SPA 和授权服务器之间的代理,可以很容易地降低这种风险。
这里(在授权代码授予类型中)流程如下所示:
- 用户点击 SPA 登陆页面上的登录按钮
- 用户被重定向到授权服务器登录页面。客户端 ID 在 URL 查询参数中提供
用户输入他/她的凭据并单击登录按钮。用户名和密码将使用 HTTP POST 发送到授权服务器。凭据应在请求正文或标头中发送,而不是在 URL 中(因为 URL 记录在浏览器历史记录和应用程序服务器中)。此外,应设置正确的缓存 HTTP 标头,以便不缓存凭据:Cache-Control: no-cache, no-store, Pragma: no-cache, Expires: 0
授权服务器根据用户数据库(例如 LDAP 服务器)对用户进行身份验证,其中用户名和用户密码的哈希(哈希算法,如 Argon2、PBKDF2、Bcrypt 或 Scrypt)与随机盐一起存储
- 认证成功后,授权服务器将根据 URL 查询参数中提供的客户端 ID 从其数据库中检索重定向 URL。重定向 URL 是资源服务器 URL
- 然后用户将被重定向到资源服务器端点,并在 URL 查询参数中使用授权代码
- 然后资源服务器将向授权服务器发出 HTTP POST 请求以获取访问令牌。授权码、客户端 ID、客户端密码应该放在请求正文中。 (应使用上述适当的缓存标头)
- 授权服务器将在响应正文或标头中返回访问令牌和刷新令牌(使用上述适当的缓存标头)
- 资源服务器现在将通过将域属性设置为
Domain=example.com 的适当cookie 将用户(HTTP 响应代码302)重定向到SPA URL(假设资源服务器和UI 都在@987654326 的子域上@)。授权服务器的域无关紧要,因为它没有设置任何 cookie。
同样,访问令牌刷新的请求可以发送到代理组件,代理组件将从cookie中读取刷新和访问令牌,并使用提取的令牌、客户端ID和访问权限调用授权服务器api客户端密码。
【解决方案4】:
最后,我们决定采用不同的解决方案。当 JWT 生命周期结束时,我们会显示一个通知会话已超时的模式,并带有 2 个按钮,一个用于注销,一个用于保留会话。当用户点击“保持会话”时,将打开新的选项卡/弹出窗口,用户在 IDP 中通过再次提供其凭据或在 IDP 会话仍处于活动状态时自动进行重新身份验证。
所以流程是:
JWT lifetime ends -> 'keep session' in modal chose -> open new tab/popup-window with IDP login form -> successfully authenticated -> redirect back to app -> store token in browser's storage -> close popup-window/tab with window.close() -> get new token from storage and use it in next calls
因为我们使用新的弹出窗口/标签来重新认证,所以 3rd 方 cookie 没有问题。
这也带来了一个巨大的优势。用户无论何时返回应用程序都不会丢失他的工作,因为模态将在那里等待。我想,另外它让我们认识了Re-authenticing accessibility success criterion (level AAA)
成功标准 2.2.5 重新认证
当经过身份验证的会话到期时,用户可以在重新进行身份验证后继续进行活动而不会丢失数据。