Rails 7部署至Heroku时无法验证CSRF token authenticity报错
Rails应用Heroku部署后CSRF校验失败、登录401问题
问题表现
应用本地localhost运行正常,部署到已配置SSL证书、未使用Cloudflare的Heroku环境后,登录操作异常,日志输出如下:
Error R14 (Memory quota exceeded) Started POST "/users/sign_in?locale=en" for 115.31.132.11 at 2022-07-04 15:19:04 +0000 Processing by Users::SessionsController#create as HTML Parameters: {"authenticity_token"=>"gLidqKg8CS6bv....", "user"=>{"email"=>"test@gmail.com", "password"=>"[FILTERED]", "remember_me"=>"1"}, "commit"=>"Log in", "locale"=>"en"} Can't verify CSRF token authenticity. Started GET "/profiles?locale=en" for XXX.XXX.XXX.XXX at 2022-07-04 16:40:43 +0000 Processing by ProfilesController#index as HTML Parameters: {"locale"=>"en"} Completed 401 Unauthorized in 84ms (Allocations: 1320) Started GET "/users/sign_in?locale=en" for X.X.X.X at 2022-07-04 16:40:43 +0000 Processing by Users::SessionsController#new as HTML
日志显示POST请求已携带CSRF参数,Heroku环境变量SECRET_TOKEN配置正确。
现有配置
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base protect_from_forgery end
config/environments/production.rb相关配置:
config.force_ssl = false config.cache_store = :redis_cache_store, { namespace: 'cache', expires_in: 24.hours, driver: :hiredis, url: ENV.fetch("REDIS_CACHE_URL") { "redis://localhost:6379/1" }, } config.session_store :cookie_store, expire_after: 1.year, domain: :all
排查确认问题和domain: :all配置直接相关,但业务必须保留该配置,以支持fr.dashboard.example.com、es.dashboard.example.com等多子域会话共享。
问题根因
Rails中domain: :all的逻辑是自动提取当前访问域名的根域,设置为session cookie的作用域。如果直接使用Heroku默认分配的xxx.herokuapp.com域名访问,Rails会把cookie域设置为公共后缀herokuapp.com,所有主流浏览器都会出于安全策略拦截这类公共后缀下的cookie写入,导致session无法在请求间正常传递,服务端拿不到session中存储的CSRF token,即使请求参数里带了token也会校验失败,最终返回401未授权。
日志里的Error R14 (Memory quota exceeded)是dyno内存超出配额的告警,和本次登录失败问题无直接关联,若修复后仍有该告警可单独优化内存占用或升级dyno规格。
修复方案
在保留多子域会话共享能力的前提下,按以下步骤调整即可解决:
- 为Heroku应用绑定自有根域名(比如
example.com),将所有业务子域(fr.dashboard.example.com、es.dashboard.example.com等)都解析到该Heroku应用,不要直接使用默认的herokuapp.com域名对外提供服务。 - 修改生产环境session_store配置,显式指定顶级域长度,不要直接裸用
domain: :all:
config.session_store :cookie_store, expire_after: 1.year, domain: :all, tld_length: 2, # 对应example.com这类双段根域,若使用example.com.cn这类更长后缀则调整为3 secure: Rails.env.production?, # HTTPS环境下仅传输cookie,提升安全性 httponly: true # 禁止前端JS读取cookie,降低XSS风险
- 由于环境已经配置了SSL证书,建议把
config.force_ssl改为true,强制所有请求走HTTPS,避免cookie被明文窃听。 - 部署更新后,清空浏览器本地存储的旧域名cookie,再重新测试登录流程即可。
内容的提问来源于stack exchange,提问作者sparkle
相关产品推荐
相关产品推荐

