Oauth2 隐式授权流和第 3 方 cookie 中的刷新令牌
Refreshing token in Oauth2 Implicit Grant Flow and 3rd party cookies
我想知道如何处理 Oauth2 隐式授权中的刷新令牌
2019 年主流浏览器默认禁用第 3 方 cookie。
一些细节:
当前设置:
UI ui.example.com
下的 SPA 应用
uaa.api.example.com
下的身份提供者(CloudFoundry 的 UAA)
场景:
当用户登录时,身份提供者会在重定向的 Location
header 中使用域 uaa.api.example.com
和 returns JWT 的用户详细信息设置 cookie。
JWT存储在local storage中(for ui.example.com
),但是有效期只有1h所以我想刷新一下。
可以使用发送到 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 将再次存储在本地存储中。
问题:
当允许第三方 cookie 时,浏览器会将 IDP 的 cookie 添加到 iframe 发出的请求中,因此流程有效并且我在响应中获得了新令牌。
当在浏览器设置中禁用第三方 Cookie 时,iframe 无法访问自己的 cookie,因此返回错误 login_required
而不是新的 JWT。无法通过 iframe 访问 cookie 导致无法使用令牌更新
问题:
我的第 3 方 cookie 问题有什么解决方案吗?
如果没有,是否有任何可用于登录和刷新令牌的隐式授权流程和 SPA 替代方案?
因为您的应用程序和身份服务器托管在不同的域中。这意味着您的应用程序正在进行跨源身份验证。跨域认证是通过第三方cookie实现的,禁用第三方cookie会导致跨域认证失败。
回答您的问题:
我的第 3 方 cookie 问题有什么解决方案吗?
Host both your application and the identity server under the same
domain. You can use the subdomain in that case.
如果没有,是否有任何可用于登录和刷新令牌的隐式授权流程和 SPA 替代方案?
No
解法:
我对 CloudFoundry 不熟悉。不确定他们是否支持。您可以通过在身份提供者端启用自定义域来解决此问题。因此,您的应用程序和身份提供者都将位于同一域中,并且 cookie 将被视为第一方。例如,将您的应用程序托管在 https://acme.com and set your identity provider custom domain as https://login.acme.com
Question:
Is there any solution for my issue with 3rd party cookies?
如果您在您的应用程序和您的 IDP 之间使用相同的顶级域,那么当第 3 方 cookie 被禁用时,您应该没有问题。
This link 还详细说明了使用跨域策略的成功与否。
If not, are there any alternatives for Implicit Grant flow and SPA
that I could use to sign in and refresh tokens?
我以前没有使用过 CloudFoundry,但是大多数大型 OAuth2.0 提供商都提供 public 客户端功能,其中 public 客户端(例如您的 SPA)不需要客户端密码来获取访问/刷新令牌。这允许 public 客户端使用 Authorisation Code Grant 允许通过刷新令牌刷新令牌,从而避免使用 HTTP 重定向和 cookie 的 silent auth 技术。
问题的根源在于使用 iframe
和隐式授权类型。
我认为您使用 iframe 的原因是为了跨域访问 cookie。现在,避免使用 iframe
的最简单方法是将 cookie 的域设置为 Domain=example.com
,并在 example.com
上同时设置 UI 和授权服务器。如果由于某种原因,您不能这样做,您需要采用以下方法。
推荐选项
隐式授权类型不安全。虽然这个问题不是关于grant types的利弊,但是为了给我要解释的选项做背景铺垫,我简单列举一下我说Implicit flow is not secured的原因:
- 缺少通过提供客户端密码和授权码进行客户端身份验证的步骤。安全性较低
- 访问令牌作为 URL 片段发回(这样令牌就不会发送到服务器),它将继续保留在浏览器历史记录中
- 如果发生 XSS 攻击,恶意脚本可以很好地将令牌发送到攻击者控制的远程服务器
因此,推荐的选项是使用授权码授予类型。在 SPA(单页应用程序)中不使用授权码的原因之一是,它要求将客户端机密存储在浏览器中,我们知道浏览器无法保密。通过在服务器端使用代理组件(可以嵌入资源服务器)来保存客户端机密并充当 SPA 和授权服务器之间的代理,可以很容易地减轻这种风险。
此处(在授权代码授予类型中)流程如下所示:
- 用户点击 SPA 登陆页面上的登录按钮
- 用户被重定向到授权服务器登录页面。客户端 ID 在 URL 查询参数
中提供
用户输入他/她的凭据并单击登录按钮。用户名和密码将使用 HTTP POST 发送到授权服务器。凭据应在请求 body 或 header 中发送,而不是在 URL 中发送(因为 URL 记录在浏览器历史记录和应用程序服务器中)。此外,应设置正确的缓存 HTTP headers,以便不缓存凭据:Cache-Control:no-cache,no-store,Pragma:no-cache,过期:0
授权服务器根据用户数据库(例如 LDAP 服务器)对用户进行身份验证,其中存储了用户名和用户密码的哈希值(哈希算法,如 Argon2、PBKDF2、Bcrypt 或 Scrypt)随机加盐
- 身份验证成功后,授权服务器会根据 URL 查询参数中提供的客户端 ID 从其数据库中检索重定向 URL。重定向 URL 是资源服务器 URL
- 然后用户将被重定向到资源服务器端点,在 URL 查询参数中使用授权码
- 然后,资源服务器将向授权服务器发出 HTTP POST 请求以获取访问令牌。授权代码、客户端 ID、客户端密码应包含在请求 body 中。 (应使用如上所述的适当缓存 headers)
- 授权服务器将return访问令牌和刷新令牌作为响应body或header(如上所述使用适当的缓存header)
- 资源服务器现在会将用户(HTTP 响应代码 302)重定向到 SPA URL,方法是将具有域属性的适当 cookie 设置为
Domain=example.com
(假设资源服务器和 UI 都在 example.com
的 sub-domains 上)。授权服务器的域无关紧要,因为它没有设置任何 cookie。
同理,访问令牌刷新请求可以发送到代理组件,代理组件会从cookie中读取刷新和访问令牌,并使用提取的令牌调用授权服务器api 、客户端 ID 和客户端密码。
最后,我们决定采用不同的解决方案。当 JWT 生命周期结束时,我们会显示一个模式通知会话已超时,并带有 2 个按钮,一个用于注销,一个用于保持会话。当用户点击“保持会话”时,将打开新的 tab/popup-window,在 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
因为我们使用新的 popup-window/tab 重新验证,所以第 3 方 cookie 没有问题。
这也提供了一个巨大的优势。用户无论何时回到应用程序都不会丢失他的工作,因为模态将在那里等待。我想,另外它让我们认识了 Re-authenticing accessibility success criterion (level AAA)
Success Criterion 2.2.5 Re-authenticating
When an authenticated session expires, the user can continue the activity without loss of data after re-authenticating.
我想知道如何处理 Oauth2 隐式授权中的刷新令牌 2019 年主流浏览器默认禁用第 3 方 cookie。
一些细节:
当前设置:
UI
ui.example.com
下的 SPA 应用
uaa.api.example.com
下的身份提供者(CloudFoundry 的 UAA)
场景:
当用户登录时,身份提供者会在重定向的
Location
header 中使用域uaa.api.example.com
和 returns JWT 的用户详细信息设置 cookie。JWT存储在local storage中(for
ui.example.com
),但是有效期只有1h所以我想刷新一下。可以使用发送到 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 将再次存储在本地存储中。
问题:
当允许第三方 cookie 时,浏览器会将 IDP 的 cookie 添加到 iframe 发出的请求中,因此流程有效并且我在响应中获得了新令牌。
当在浏览器设置中禁用第三方 Cookie 时,iframe 无法访问自己的 cookie,因此返回错误
login_required
而不是新的 JWT。无法通过 iframe 访问 cookie 导致无法使用令牌更新
问题:
我的第 3 方 cookie 问题有什么解决方案吗?
如果没有,是否有任何可用于登录和刷新令牌的隐式授权流程和 SPA 替代方案?
因为您的应用程序和身份服务器托管在不同的域中。这意味着您的应用程序正在进行跨源身份验证。跨域认证是通过第三方cookie实现的,禁用第三方cookie会导致跨域认证失败。
回答您的问题:
我的第 3 方 cookie 问题有什么解决方案吗?
Host both your application and the identity server under the same domain. You can use the subdomain in that case.
如果没有,是否有任何可用于登录和刷新令牌的隐式授权流程和 SPA 替代方案?
No
解法:
我对 CloudFoundry 不熟悉。不确定他们是否支持。您可以通过在身份提供者端启用自定义域来解决此问题。因此,您的应用程序和身份提供者都将位于同一域中,并且 cookie 将被视为第一方。例如,将您的应用程序托管在 https://acme.com and set your identity provider custom domain as https://login.acme.com
Question:
Is there any solution for my issue with 3rd party cookies?
如果您在您的应用程序和您的 IDP 之间使用相同的顶级域,那么当第 3 方 cookie 被禁用时,您应该没有问题。 This link 还详细说明了使用跨域策略的成功与否。
If not, are there any alternatives for Implicit Grant flow and SPA that I could use to sign in and refresh tokens?
我以前没有使用过 CloudFoundry,但是大多数大型 OAuth2.0 提供商都提供 public 客户端功能,其中 public 客户端(例如您的 SPA)不需要客户端密码来获取访问/刷新令牌。这允许 public 客户端使用 Authorisation Code Grant 允许通过刷新令牌刷新令牌,从而避免使用 HTTP 重定向和 cookie 的 silent auth 技术。
问题的根源在于使用 iframe
和隐式授权类型。
我认为您使用 iframe 的原因是为了跨域访问 cookie。现在,避免使用 iframe
的最简单方法是将 cookie 的域设置为 Domain=example.com
,并在 example.com
上同时设置 UI 和授权服务器。如果由于某种原因,您不能这样做,您需要采用以下方法。
推荐选项
隐式授权类型不安全。虽然这个问题不是关于grant types的利弊,但是为了给我要解释的选项做背景铺垫,我简单列举一下我说Implicit flow is not secured的原因:
- 缺少通过提供客户端密码和授权码进行客户端身份验证的步骤。安全性较低
- 访问令牌作为 URL 片段发回(这样令牌就不会发送到服务器),它将继续保留在浏览器历史记录中
- 如果发生 XSS 攻击,恶意脚本可以很好地将令牌发送到攻击者控制的远程服务器
因此,推荐的选项是使用授权码授予类型。在 SPA(单页应用程序)中不使用授权码的原因之一是,它要求将客户端机密存储在浏览器中,我们知道浏览器无法保密。通过在服务器端使用代理组件(可以嵌入资源服务器)来保存客户端机密并充当 SPA 和授权服务器之间的代理,可以很容易地减轻这种风险。
此处(在授权代码授予类型中)流程如下所示:
- 用户点击 SPA 登陆页面上的登录按钮
- 用户被重定向到授权服务器登录页面。客户端 ID 在 URL 查询参数 中提供
用户输入他/她的凭据并单击登录按钮。用户名和密码将使用 HTTP POST 发送到授权服务器。凭据应在请求 body 或 header 中发送,而不是在 URL 中发送(因为 URL 记录在浏览器历史记录和应用程序服务器中)。此外,应设置正确的缓存 HTTP headers,以便不缓存凭据:Cache-Control:no-cache,no-store,Pragma:no-cache,过期:0
授权服务器根据用户数据库(例如 LDAP 服务器)对用户进行身份验证,其中存储了用户名和用户密码的哈希值(哈希算法,如 Argon2、PBKDF2、Bcrypt 或 Scrypt)随机加盐
- 身份验证成功后,授权服务器会根据 URL 查询参数中提供的客户端 ID 从其数据库中检索重定向 URL。重定向 URL 是资源服务器 URL
- 然后用户将被重定向到资源服务器端点,在 URL 查询参数中使用授权码
- 然后,资源服务器将向授权服务器发出 HTTP POST 请求以获取访问令牌。授权代码、客户端 ID、客户端密码应包含在请求 body 中。 (应使用如上所述的适当缓存 headers)
- 授权服务器将return访问令牌和刷新令牌作为响应body或header(如上所述使用适当的缓存header)
- 资源服务器现在会将用户(HTTP 响应代码 302)重定向到 SPA URL,方法是将具有域属性的适当 cookie 设置为
Domain=example.com
(假设资源服务器和 UI 都在example.com
的 sub-domains 上)。授权服务器的域无关紧要,因为它没有设置任何 cookie。
同理,访问令牌刷新请求可以发送到代理组件,代理组件会从cookie中读取刷新和访问令牌,并使用提取的令牌调用授权服务器api 、客户端 ID 和客户端密码。
最后,我们决定采用不同的解决方案。当 JWT 生命周期结束时,我们会显示一个模式通知会话已超时,并带有 2 个按钮,一个用于注销,一个用于保持会话。当用户点击“保持会话”时,将打开新的 tab/popup-window,在 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
因为我们使用新的 popup-window/tab 重新验证,所以第 3 方 cookie 没有问题。
这也提供了一个巨大的优势。用户无论何时回到应用程序都不会丢失他的工作,因为模态将在那里等待。我想,另外它让我们认识了 Re-authenticing accessibility success criterion (level AAA)
Success Criterion 2.2.5 Re-authenticating
When an authenticated session expires, the user can continue the activity without loss of data after re-authenticating.