使用跨站点 cookie 从 Chrome 扩展 post 到 Rails API
Using cross-site cookies to post to Rails API from Chrome extension
我构建了一个 Chrome 扩展程序,可以将网络内容保存到我的 Rails 应用程序中。最初我能够依靠现有的 Rails/Devise 用户会话来确保内容被保存到正确的用户,只要在我的 API 控制器上打开 CORS 设置(见下面的代码)。只要用户已登录,AJAX 从 Chrome 扩展程序调用我的网站就会得到正确的身份验证,无论该扩展程序在哪个站点上使用。
但是,在 2020 年初,Chrome 对他们处理跨站点请求的方式进行了更改(请参阅 here, here, and here)。具体来说,cookie 的 SameSite 属性现在默认为 'Lax' 而不是 'None',因此要使用跨站点 cookie,需要将 cookie 设置显式设置为 SameSite=None; Secure
。
Rails'自己的用户会话 cookie 没有 SameSite=None; Secure
设置,因此使用 Rails 会话验证我的 Chrome 扩展程序的请求不再是一个选项。
我的解决方法是在用户登录应用程序时生成我自己的 API 身份验证 cookie,这确实应用了必要的 SameSite=None; Secure
。我能够使用此 cookie 验证来自我的 Chrome 分机的 API 调用,一切正常。
然后在 2020 年 9 月上旬突然停止工作。 Rails 不再从 Chrome 扩展请求中读取跨站点 cookie。没有错误或警告,值为 null。
API 控制器:
# This gets called when user logs into app:
def set_cross_site_cookie
# NOTE: Won't work in dev because secure = true
cookies[:foo_cookie] = {
value: 'bar',
expires: 1.year.from_now,
same_site: :none, # Required in order to access from Chrome extension on different site
secure: true # Required in order to access from Chrome extension on different site
}
cookie = cookies[:foo_cookie]
render json: {cookie: cookie}
end
# This SHOULD work when called from our Chrome extension:
def get_cross_site_cookie
# Add headers to allow CORS requests
# SEE:
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Request-Method'] = %w{GET POST OPTIONS}.join(",")
cookie = cookies[:foo_cookie]
render json: {cookie: cookie}
end
Rails 5、机架2.1
(注意:为了使用选项 same_site: none
设置 Rails cookie,您显然需要使用高于 2.1.0 的机架版本 - 请参见:https://github.com/rails/rails/pull/28297#issuecomment-600566751)
有人知道发生了什么吗?
我仍然不知道为什么 cross-site cookie 突然停止工作,但我是这样破解它的:
解决方法是使用 Chrome extension cookie API 将我的 Rails API 身份验证 cookie 读取到 Chrome 的本地存储中。由于我们可以在扩展清单中启用对任何特定站点的 cookie 的访问,因此实际上它们是否 cross-site cookie 并不重要。
一旦 API cookie 被读入存储,我们就可以将其作为身份验证令牌与每个请求一起传递,基本上将其用作 pseudo-cookie.
所以完整的流程是用户点击扩展按钮,扩展根据它对该域的显式 cookie 权限读取 API 身份验证 cookie,如果 cookie 丢失或过时,它会强制用户登录。如果 cookie 有效,它会在每个 API 调用的参数或 headers 中作为身份验证令牌传递。
PRE-FLIGHT 选项请求的旁注:
您可能还必须处理将与某些 cross-site AJAX 一起发送的 OPTIONS pre-flight 请求(我认为这只是 content-type JSON 发布但不要引用我的话),因为它们会在 Rails 中触发 ActionController::RoutingError (No route matches [OPTIONS])
错误。推荐的答案是使用 rack-cors gem,确实解决了问题。
参见:
- Why is an OPTIONS request sent and can I disable it - Stack Overflow ***
- How to respond to OPTIONS HTTP Method in rails-api - Stack Overflow
- Rails Responds with 404 on CORS Preflight Options Request - Stack Overflow
- Perform HTTP OPTIONS request from Rails 5 for CORS pre-flight or otherwise - Stack Overflow
我构建了一个 Chrome 扩展程序,可以将网络内容保存到我的 Rails 应用程序中。最初我能够依靠现有的 Rails/Devise 用户会话来确保内容被保存到正确的用户,只要在我的 API 控制器上打开 CORS 设置(见下面的代码)。只要用户已登录,AJAX 从 Chrome 扩展程序调用我的网站就会得到正确的身份验证,无论该扩展程序在哪个站点上使用。
但是,在 2020 年初,Chrome 对他们处理跨站点请求的方式进行了更改(请参阅 here, here, and here)。具体来说,cookie 的 SameSite 属性现在默认为 'Lax' 而不是 'None',因此要使用跨站点 cookie,需要将 cookie 设置显式设置为 SameSite=None; Secure
。
Rails'自己的用户会话 cookie 没有 SameSite=None; Secure
设置,因此使用 Rails 会话验证我的 Chrome 扩展程序的请求不再是一个选项。
我的解决方法是在用户登录应用程序时生成我自己的 API 身份验证 cookie,这确实应用了必要的 SameSite=None; Secure
。我能够使用此 cookie 验证来自我的 Chrome 分机的 API 调用,一切正常。
然后在 2020 年 9 月上旬突然停止工作。 Rails 不再从 Chrome 扩展请求中读取跨站点 cookie。没有错误或警告,值为 null。
API 控制器:
# This gets called when user logs into app:
def set_cross_site_cookie
# NOTE: Won't work in dev because secure = true
cookies[:foo_cookie] = {
value: 'bar',
expires: 1.year.from_now,
same_site: :none, # Required in order to access from Chrome extension on different site
secure: true # Required in order to access from Chrome extension on different site
}
cookie = cookies[:foo_cookie]
render json: {cookie: cookie}
end
# This SHOULD work when called from our Chrome extension:
def get_cross_site_cookie
# Add headers to allow CORS requests
# SEE:
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Request-Method'] = %w{GET POST OPTIONS}.join(",")
cookie = cookies[:foo_cookie]
render json: {cookie: cookie}
end
Rails 5、机架2.1
(注意:为了使用选项 same_site: none
设置 Rails cookie,您显然需要使用高于 2.1.0 的机架版本 - 请参见:https://github.com/rails/rails/pull/28297#issuecomment-600566751)
有人知道发生了什么吗?
我仍然不知道为什么 cross-site cookie 突然停止工作,但我是这样破解它的:
解决方法是使用 Chrome extension cookie API 将我的 Rails API 身份验证 cookie 读取到 Chrome 的本地存储中。由于我们可以在扩展清单中启用对任何特定站点的 cookie 的访问,因此实际上它们是否 cross-site cookie 并不重要。
一旦 API cookie 被读入存储,我们就可以将其作为身份验证令牌与每个请求一起传递,基本上将其用作 pseudo-cookie.
所以完整的流程是用户点击扩展按钮,扩展根据它对该域的显式 cookie 权限读取 API 身份验证 cookie,如果 cookie 丢失或过时,它会强制用户登录。如果 cookie 有效,它会在每个 API 调用的参数或 headers 中作为身份验证令牌传递。
PRE-FLIGHT 选项请求的旁注:
您可能还必须处理将与某些 cross-site AJAX 一起发送的 OPTIONS pre-flight 请求(我认为这只是 content-type JSON 发布但不要引用我的话),因为它们会在 Rails 中触发 ActionController::RoutingError (No route matches [OPTIONS])
错误。推荐的答案是使用 rack-cors gem,确实解决了问题。
参见:
- Why is an OPTIONS request sent and can I disable it - Stack Overflow ***
- How to respond to OPTIONS HTTP Method in rails-api - Stack Overflow
- Rails Responds with 404 on CORS Preflight Options Request - Stack Overflow
- Perform HTTP OPTIONS request from Rails 5 for CORS pre-flight or otherwise - Stack Overflow