You might be surprised by Rails’ behavior when rendering a partial via Turbo.

There is no session context - thus form_tag won’t render the “authenticity token” hidden field - it will silently omit it. This behavior was changed around 2021, and you can check out this lengthy discussion about the problem.

Rails Turbo will automagically attach the CSRF token to the request obtained from a meta tag:

prepareRequest(request) {
    if (!request.isSafe) {
      const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
      if (token) {
        request.headers["X-CSRF-Token"] = token;
      }
    }
    if (this.requestAcceptsTurboStreamResponse(request)) {
      request.acceptResponseType(StreamMessage.contentType);
    }
  }

So if you render a form via Turbo which you intend to submit further without Turbo, you will need to re-attach the CSRF token in a similar manner. You can use a Stimulus controller as shown in the example below:

import { Controller } from "@hotwired/stimulus";
 import Rails from "@rails/ujs";
 
 export default class extends Controller {
   private csrfParam = Rails.csrfParam();
 
   connect() {
     let form = this.element;
 
     // assuming page can be refreshed by hard refresh and form will by
     // rendered with the session context - thus with csrf token
     if (form.querySelector(`input[name='${this.csrfParam}']`) === null) {
       form.appendChild(this.authenticityTokenInput);
     }
   }
 
   get authenticityTokenInput() {
     const input = document.createElement("input");
 
     input.type = "hidden";
     input.name = this.csrfParam;
     input.autocomplete = "off";
     input.value = Rails.csrfToken();
 
     return input;
   }
 }

Then you attach this controller to your form via data-controller (the Stimulus way).