Turbo stream and authenticity token

After updating a record, I use a turbo stream to send out some new command buttons to all users involved in that topic. It is a button_to in this case.

When I click on the button, I get the error ActionController::InvalidAuthenticityToken and get logged out. Everything works fine, if I load the modal by clicking on the record in the list items.

If anyone has any ideas about what could be going wrong, I am all ears.

Related to:

  1. Turbo stream partial with button_to tag throws ActionView::Template::Error · Issue #243 · hotwired/turbo-rails · GitHub
  2. Stop failing GSRF token generation when session is disabled by casperisfine · Pull Request #43427 · rails/rails · GitHub
  3. button_to: Support `authenticity_token:` option by seanpdoyle · Pull Request #43417 · rails/rails · GitHub
1 Like

button_to rails helper creates a form, which includes a unique authenticity token (hidden form field) linked to the users session. It’s likely the created button_to contains a hidden field with an authenticity token not valid for the current session you are testing.

@tleish the problem is that there is no authenticity token generated. Not even adding authenticity_token: true provides that for me.

I know what the problem is, I am interested in solutions. Currently trying out mrujs

This solutions works for me:

# config/importmap.rb

pin "mrujs", to: "https://ga.jspm.io/npm:mrujs@0.7.1/dist/mrujs.module.js"
pin "morphdom", to: "https://ga.jspm.io/npm:morphdom@2.6.1/dist/morphdom.js"
// app/javascript/application.js

import "@hotwired/turbo-rails"
import "controllers"
import mrujs from "mrujs"
mrujs.start()
// app/javascript/controllers/authentic_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    if (this.element.querySelector("input[name='authenticity_token']") == null) {
      this.element.closest("form").appendChild(this.authenticityToken)
    }
  }

  get authenticityToken() {
    const input = document.createElement("input")

    input.type = "hidden"
    input.name = "authenticity_token"
    input.autocomplete = "off"
    input.value = window.mrujs.csrfToken()

    return input
  }
}

Then in the HTML:

  <%= button_to I18n.t("model.buttons.collect_payment"),
                          method: :post,
                          class: "btn btn-green",
                          data: {
                            controller: "authentic",
                            action: "click->toggle#hide",
                          } %>

Now the authenticity token is added properly

1 Like

Bumping on that one.

I am basically doing a fetch to the backend with an array as content from a Stimulus controller :

orderUpdater(array){		
	fetch("/order_updater",
		{
			method: "POST",
			body: JSON.stringify({content: array}),
			headers: {
	              'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content,
	              'Content-Type': 'application/json'          
	            },  
	        credentials: "same-origin",
		}).then(response => response.json())
	    .then(data => {	console.log(data.message) });
}

It works well the first time a user is logged in through Devise. Though if the user unlog and relog, then the above code no longer works.

(edit: there is no HTML form in the front end, basically I gather HTML elements ID, that can be dragged with Shopify’s Draggable.js and ask the backend to save new order)

I have watched the Authenticity Token in head, and as expected for an SPA, it doesn’t change across Devise logins / log outs. But why does the backend expects a different token at some point ?