Rails 8.2: CSRF Without Tokens
Sec-Fetch-Site replaces the authenticity token, Rails.app.revision arrives for monitoring, and Argon2 lands in has_secure_password. What actually changes in your application.
The short version
- CSRF protection can now use the
Sec-Fetch-Siteheader instead of authenticity tokens. New 8.2 apps default to header-only. - Existing apps default to
:header_or_legacy_token, which falls back to tokens. Nothing breaks on upgrade. Rails.app.revisiongives you a deployment identifier for error reporting, monitoring and cache keys without wiring one up yourself.- Argon2 is available for
has_secure_password, removing BCrypt’s 72-byte password length limit.
Rails 8.2 is a smaller release than Rails 8.0 was, and it contains one change of real architectural interest: authenticity tokens, a fixture of Rails applications since 2007, are no longer the primary CSRF defence.
Header-based CSRF protection
Modern browsers send a Sec-Fetch-Site header on every request, describing the relationship between the origin that initiated the request and the origin receiving it: same-origin, same-site, cross-site, or none for a directly initiated navigation.
That is precisely the information CSRF protection needs, supplied by the browser and not forgeable by page JavaScript. Rails 8.2 uses it directly.
Two strategies are available through protect_from_forgery using::
:header_only— verifies using the header alone and rejects requests without a valid one. This is the default for new Rails 8.2 applications.:header_or_legacy_token— checks the header first, and falls back to authenticity token verification when the header is missing or has the valuenone, logging when the fallback occurs. This remains the default for existing applications.
Legitimate cross-site requests — OAuth callbacks, third-party embeds, payment provider returns — are handled by listing their origins explicitly:
protect_from_forgery trusted_origins: %w[ https://accounts.google.com ]
InvalidAuthenticityToken is deprecated in favour of InvalidCrossOriginRequest. If you rescue that exception anywhere — and many applications do, to render a friendly page — that is a line to update.
Why this is a genuine improvement
Authenticity tokens work, and they carry a persistent cost. Every form needs one embedded, every AJAX request needs it read from a meta tag and attached, the token is tied to the session so it breaks when a session expires behind a cached page, and API clients and background-tab forms produce a steady trickle of confusing failures.
Header-based verification removes all of that. There is nothing to embed, nothing to attach, nothing to expire. The browser supplies the information on every request and it cannot be spoofed by the attacking page.
One local-development wrinkle worth knowing. Browsers only send Sec-Fetch-Site from a secure context. Rails accounts for this: when the request is over plain HTTP and the app is not configured to force SSL, requests missing the header are allowed. The Origin check still runs in every case.
Rails 8.2 also adds structured logging events for CSRF outcomes, which makes the migration measurable. Watch for csrf_request_blocked.action_controller to catch legitimate traffic you have not yet allowlisted, and use the fallback logging under :header_or_legacy_token to see how much of your traffic still needs tokens before switching to header-only.
How to migrate
- Upgrade and leave the default (
:header_or_legacy_token) in place. Everything continues to work. - Watch the fallback logging in production for a period long enough to cover your real traffic mix, including older browsers and any API clients.
- Add
trusted_originsfor every legitimate cross-site source you find. - Switch to
:header_onlyonce the fallback rate is negligible. - Update anything rescuing
InvalidAuthenticityToken.
Rails.app and Rails.app.revision
Rails.app is now an alias for Rails.application, which is a small ergonomic improvement to something typed constantly.
More useful is Rails.app.revision, which provides a version identifier for the running deployment, read by default from a REVISION file or the local git SHA. Nearly every production Rails application has hand-rolled some version of this for error reporting, and having it standard means monitoring tools and libraries can rely on it existing.
The obvious uses: tagging exceptions with the deployment that produced them, so an error spike can be attributed to a specific release; including it in cache keys, so a deploy invalidates caches whose shape changed; and reporting it in health check output.
Rails.app.creds also arrives, providing combined access to credentials stored either in the environment or in the encrypted credentials file, with require and option methods for mandatory and optional values. It replaces the conditional lookup logic that appears in most mature applications.
Argon2 for has_secure_password
Password hashing gains a modern option through algorithm: :argon2.
The concrete reason to care is BCrypt’s 72-byte input limit. Bytes beyond that are silently ignored, which means a long passphrase is effectively truncated — and users are increasingly encouraged by password managers to use exactly those. Argon2 has no such limit. It is also memory-hard by design, which makes large-scale parallel cracking on specialised hardware considerably more expensive.
A new ActiveModel::SecurePassword.register_algorithm API allows registering custom hashing algorithms, which is the right shape for a decision that changes every decade or so.
For existing applications, the sensible migration is to rehash on next successful login rather than attempting a bulk migration — you cannot rehash a password you do not have in plaintext.
Other changes worth noting
- Collection rendering accepts a block, executed for each rendered item, which removes a partial in several common cases.
- The
protect_from_forgerydefault of:null_sessionis deprecated for being inconsistent with the controller-level default of:exception. Passwith: :null_sessionexplicitly to silence the warning, or opt into the new behaviour. - Assorted deprecation removals from earlier 8.x releases. If you upgraded through 8.0 and 8.1 attentively, there is nothing here.
What this says about where Rails is going
Read together, the 8.x releases have a consistent theme, and 8.2 continues it. Rails 8.0 removed the reasons most teams reached for a managed queue, cache and cable service. Rails 8.2 removes a token round-trip the browser can now handle natively, a version-identifier helper every application had written, and a credentials lookup pattern that appeared in every mature codebase.
The pattern is not adding capability so much as absorbing the things applications kept building anyway. That is a mature framework behaving well, and it has a practical implication worth acting on: before writing infrastructure code, check whether the current release has absorbed it. A surprising amount of what sits in lib/ in a long-lived Rails application is a solved problem that was not solved when the code was written.
It also means upgrade discipline pays a compounding dividend. Each release you take on time is a small amount of code you get to delete, and each one you skip is code you keep maintaining for another year.
Should you upgrade
Yes, on the usual schedule. This is a point release with a conservative default for existing applications: the CSRF change does not activate in header-only mode unless you choose it, and everything else is additive.
The migration work worth planning is the CSRF strategy switch, which is not urgent but is worth doing deliberately rather than leaving on the fallback indefinitely. Give yourself a real observation window before flipping it.
Boolean Solutions experience with Rails upgrades
We run Rails in production across a range of application ages and we handle version upgrades regularly. The 8.x line has been unusually kind to upgraders — conservative defaults, clear deprecation paths, and release notes that say plainly what changes for existing applications.
Our standard approach to a change like the CSRF strategy is to instrument first and switch second. The logging Rails added here makes that straightforward, and it turns a security configuration change from a leap into a measurement. If you are planning a Rails upgrade or want a second opinion on a security configuration, we are happy to help, and our security services page covers the wider picture.
Further reading
- Rails 8.2 Release Notes — the complete list, including deprecations
- Sec-Fetch-Site — MDN on the header and its values
- OWASP CSRF Prevention Cheat Sheet — the wider context for why this approach is sound
- Action Pack CHANGELOG — the implementation detail behind the CSRF change
