I have a button_to tag, with data: {turbo_method: :post}
This works.
When I change it to link_to it breaks with Can't verify CSRF token authenticity.
Is this a bug? It seems that it should work regardless according to the documentation.
In case you are wondering why I care: I am retooling an legacy app to use Turbo Rails / Hotwire, and the style sheets are already set up for this to be an anchor tag. Otherwise it wouldn't be a problem.
----- I was asked for these details -------
<%= button_to(update_header_admin_settings_printers_path, data: { turbo_method: :post },
params: @params.merge({hide_filters: @params[:hide_filters]=="true" ? "false" : "true" }),
class: "cp-btn cp-btn--small tp-filter") do %>
<i class="material-icons cp-btn__icon">filter_list</i>
<span><%= @params[:hide_filters]=="true" ? "Show " : "Hide " %> Button Filters</span>
<% end %>
<%= link_to(update_header_admin_settings_printers_path, data: { turbo_method: :post },
params: @params.merge({hide_filters: @params[:hide_filters]=="true" ? "false" : "true" }),
class: "cp-btn cp-btn--small tp-filter") do %>
<i class="material-icons cp-btn__icon">filter_list</i>
<span><%= @params[:hide_filters]=="true" ? "Show " : "Hide " %> Link Filters</span>
<% end %>
Request Header For Button:
accept
text/vnd.turbo-stream.html, text/html, application/xhtml+xml
accept-encoding
gzip, deflate, br, zstd
accept-language
en-US,en;q=0.9
connection
keep-alive
content-length
123
content-type
application/x-www-form-urlencoded;charset=UTF-8
cookie
_quote_editor_session=3ffUr0BL0QvgoKcfZ5c7MupwCX3kQOx9xgTGSEDfoi%2FB7QM3gQpYtSp0I%2FPu1%2Bf8Q19j9vf2KcTJzRExh62RwIoYIGzVWNGNpCQMHQC%2FEO%2BrsxA4HH6Gsj%2BeE5wO3l6guNCI3dIiYsxnQg%2BNHOFQcJImvP45sH4sFBNEO%2FUR3uzdJnGscEnyHXvU591baeh6kwg5nWUqf%2BFM8ImspKlJaZUWxZDsjwvMrjI4GbW5opqNXC6DRpxHc%2FEr%2FY2LsIjobdHE88AuRGKuCqZTutQRgatkS%2BUKAjI%2FZP%2FpKEU%3D--syKk0hnBUMK4fFg2--IMn4hfIICS9NkjONyq0V2g%3D%3D; _catprint_on_rails_session=s3Heh%2BRJtJTyHk8VOSUkYk%2F%2BpQ%2BtzzD6wq%2Fi9GeuDfsoZbSToggOBkg8rGk9zRjR6D6d3uC9rS%2FOdX6G8uy8u21fqQ%2Bg%2FObEtKyTJN1XhKpqpMYSA2FxeEt%2B3P4o0DtclIUPZgmu9MEpL%2FRGrSVn2zwhcq829ND6HO3Z0rbJRGa77DK3PlewLm3GLRLXgoLI21rOALfDxiAM9F0Umrcmoly1UoK%2Bq58rfd3zIOZfg90yebxTm4Bzbfvt1memyv%2FwzdRRiHExSmbQo0dO6gebH7R36KjT7iLs2pBbyHHHSQs7qxkOMgyQrhqTyLctCLOWp0Gmog0wic1oymUy%2FP%2FOB%2B%2Bv3%2BjNsEid2YX5gDrW0dk%2B%2F8Nk9ghPEshpdnwE1WELCoLqourkwfGMx4jOgEpZjX1wkSk34HxzEwRYKha4VbPyS8%2Fb7mT%2FiQz8dc46ZWzhQS8pIVj1jtCGkXi%2FZ68%3D--RpRDmpX%2FQ452aPrM--TmsoFtYmpd05Wh0EVSl94Q%3D%3D
host
localhost:5000
origin
http://localhost:5000
referer
http://localhost:5000/admin/settings/printers
sec-ch-ua
"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"
sec-ch-ua-mobile
?0
sec-ch-ua-platform
"macOS"
sec-fetch-dest
empty
sec-fetch-mode
cors
sec-fetch-site
same-origin
turbo-frame
header
user-agent
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
x-csrf-token
GvuIphHnU//L9qhzs5RSZpmQGXbYkRFGbckAaKIkBWl6kUe0IeGpJ9H8CqEFxajRaOMC9Ypz3CX80MDYVlUjRA==
x-turbo-request-id
41c1c4d0-7b0c-4813-985d-b88c22646d25
Server Log for Button:
Started POST "/admin/settings/printers/update_header" for ::1 at 2025-11-13 13:36:45 -0500
Processing by Admin::Settings::PrintersController#update_header as TURBO_STREAM
Parameters: {"authenticity_token"=>"[FILTERED]", "hide_filters"=>"true"}
Rendered admin/settings/printers/_filters.html.erb (Duration: 0.2ms | GC: 0.0ms)
Rendered admin/settings/printers/_header.html.erb (Duration: 1.1ms | GC: 0.0ms)
Completed 200 OK in 5ms (Views: 1.4ms | ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.0ms)
Request Header for Link is exactly the same EXCEPT
Content-Length 0
Server Log for Link
Started POST "/admin/settings/printers/update_header" for ::1 at 2025-11-13 13:38:54 -0500
Processing by Admin::Settings::PrintersController#update_header as TURBO_STREAM
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Content in 6ms (ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.0ms)
ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.):
actionpack (8.1.0) lib/action_controller/metal/request_forgery_protection.rb:321:in `handle_unverified_request'
actionpack (8.1.0) lib/action_controller/metal/request_forgery_protection.rb:415:in `handle_unverified_request'
actionpack (8.1.0) lib/action_controller/metal/request_forgery_protection.rb:404:in `verify_authenticity_token'
activesupport (8.1.0) lib/active_support/callbacks.rb:362:in `block in make_lambda'
activesupport (8.1.0) lib/active_support/callbacks.rb:179:in `block in call'
actionpack (8.1.0) lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
activesupport (8.1.0) lib/active_support/callbacks.rb:180:in `call'
activesupport (8.1.0) lib/active_support/callbacks.rb:560:in `block in invoke_before'
activesupport (8.1.0) lib/active_support/callbacks.rb:560:in `each'
activesupport (8.1.0) lib/active_support/callbacks.rb:560:in `invoke_before'
activesupport (8.1.0) lib/active_support/callbacks.rb:119:in `block in run_callbacks'
activesupport (8.1.0) lib/active_support/callbacks.rb:141:in `run_callbacks'
actionpack (8.1.0) lib/abstract_controller/callbacks.rb:260:in `process_action'
actionpack (8.1.0) lib/action_controller/metal/rescue.rb:36:in `process_action'
actionpack (8.1.0) lib/action_controller/metal/instrumentation.rb:76:in `block in process_action'
activesupport (8.1.0) lib/active_support/notifications.rb:210:in `block in instrument'
activesupport (8.1.0) lib/active_support/notifications/instrumenter.rb:58:in `instrument'
activesupport (8.1.0) lib/active_support/notifications.rb:210:in `instrument'
actionpack (8.1.0) lib/action_controller/metal/instrumentation.rb:75:in `process_action'
actionpack (8.1.0) lib/action_controller/metal/params_wrapper.rb:259:in `process_action'
activerecord (8.1.0) lib/active_record/railties/controller_runtime.rb:39:in `process_action'
actionpack (8.1.0) lib/abstract_controller/base.rb:157:in `process'
actionview (8.1.0) lib/action_view/rendering.rb:40:in `process'
actionpack (8.1.0) lib/action_controller/metal.rb:252:in `dispatch'
actionpack (8.1.0) lib/action_controller/metal.rb:335:in `dispatch'
actionpack (8.1.0) lib/action_dispatch/routing/route_set.rb:65:in `dispatch'
actionpack (8.1.0) lib/action_dispatch/routing/route_set.rb:50:in `serve'
actionpack (8.1.0) lib/action_dispatch/journey/router.rb:35:in `block in serve'
actionpack (8.1.0) lib/action_dispatch/journey/router.rb:86:in `block in recognize'
actionpack (8.1.0) lib/action_dispatch/journey/router.rb:66:in `each'
actionpack (8.1.0) lib/action_dispatch/journey/router.rb:66:in `recognize'
actionpack (8.1.0) lib/action_dispatch/journey/router.rb:31:in `serve'
actionpack (8.1.0) lib/action_dispatch/routing/route_set.rb:906:in `call'
railties (8.1.0) lib/rails/engine/lazy_route_set.rb:60:in `call'
rack (3.2.3) lib/rack/tempfile_reaper.rb:20:in `call'
rack (3.2.3) lib/rack/etag.rb:29:in `call'
rack (3.2.3) lib/rack/conditional_get.rb:44:in `call'
rack (3.2.3) lib/rack/head.rb:15:in `call'
actionpack (8.1.0) lib/action_dispatch/http/content_security_policy.rb:38:in `call'
rack-session (2.1.1) lib/rack/session/abstract/id.rb:274:in `context'
rack-session (2.1.1) lib/rack/session/abstract/id.rb:268:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/cookies.rb:708:in `call'
activerecord (8.1.0) lib/active_record/migration.rb:671:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/callbacks.rb:31:in `block in call'
activesupport (8.1.0) lib/active_support/callbacks.rb:101:in `run_callbacks'
actionpack (8.1.0) lib/action_dispatch/middleware/callbacks.rb:30:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/executor.rb:20:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/actionable_exceptions.rb:18:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/debug_exceptions.rb:31:in `call'
web-console (4.2.1) lib/web_console/middleware.rb:132:in `call_app'
web-console (4.2.1) lib/web_console/middleware.rb:28:in `block in call'
web-console (4.2.1) lib/web_console/middleware.rb:17:in `catch'
web-console (4.2.1) lib/web_console/middleware.rb:17:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/show_exceptions.rb:32:in `call'
railties (8.1.0) lib/rails/rack/logger.rb:41:in `call_app'
railties (8.1.0) lib/rails/rack/logger.rb:29:in `call'
propshaft (1.3.1) lib/propshaft/quiet_assets.rb:11:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/remote_ip.rb:98:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/request_id.rb:34:in `call'
rack (3.2.3) lib/rack/method_override.rb:28:in `call'
rack (3.2.3) lib/rack/runtime.rb:24:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/server_timing.rb:61:in `block in call'
actionpack (8.1.0) lib/action_dispatch/middleware/server_timing.rb:26:in `collect_events'
actionpack (8.1.0) lib/action_dispatch/middleware/server_timing.rb:60:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/executor.rb:20:in `call'
propshaft (1.3.1) lib/propshaft/server.rb:37:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/static.rb:27:in `call'
rack (3.2.3) lib/rack/sendfile.rb:131:in `call'
actionpack (8.1.0) lib/action_dispatch/middleware/host_authorization.rb:143:in `call'
railties (8.1.0) lib/rails/engine.rb:534:in `call'
puma (7.1.0) lib/puma/configuration.rb:300:in `call'
puma (7.1.0) lib/puma/request.rb:101:in `block in handle_request'
puma (7.1.0) lib/puma/thread_pool.rb:355:in `with_force_shutdown'
puma (7.1.0) lib/puma/request.rb:100:in `handle_request'
puma (7.1.0) lib/puma/server.rb:503:in `process_client'
puma (7.1.0) lib/puma/server.rb:262:in `block in run'
puma (7.1.0) lib/puma/thread_pool.rb:182:in `block in spawn_thread'
<%= csrf_meta_tags %>in your layout?