Sending non-GET requests with Fetch JavaScript API in Rails

kinopyo avatar
kinopyo

If you're using Fetch API to send a non-GET requests to a Rails controller, you may bump into the InvalidAuthenticityToken exception. It's because Rails requires a special CSRF token to validate the request, and you can pass it via X-CSRF-Token header.

Here is a working example of adding the CSRF token in the headers:

// Grab the CSRF token from the meta tag
const csrfToken = document.querySelector("[name='csrf-token']").content

fetch("/v1/articles", {
  method: "POST",
  headers: {
    "X-CSRF-Token": csrfToken, // πŸ‘ˆπŸ‘ˆπŸ‘ˆ Set the token
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ title: "awesome post" })
}).then(response => {
  if (!response.ok) { throw response; }
  return response.json()
}).then((data) => {
  console.log(data)
}).catch(error => {
  console.error("error", error)
})

In old-fashioned Rails apps, CSRF token is handled by rails-ujs transparently so there is no extra work for you.

However, if you're running Rails + React or even vanilla JavaScript where you want to fire the raw requests from the frontend, you'll need to do what the code snippet above shows: grab the CSRF token from the markup and pass it in the headers.

A note for test env

In test env, Rails won't check CSRF token for non-GET requests, and it also won't generate the meta tag for it. So you'll need to do a presence check in your JavaScript before accessing the .content. Kinda awkward. 😳

You may want to put this into a util method.

utils.js
export function getCSRFToken() { const csrfToken = document.querySelector("[name='csrf-token']") if (csrfToken) { return csrfToken.content } else { return null } }

credentials: "same-origin"

During my quick research, all examples I found on the internet have included credentials: "same-origin" in the request parameters, e.g.

fetch("/v1/articles", {
  // method, headers, body omitted
  credentials: "same-origin"
})

According to the MDN article , the default credentials value of fetch() has been changed from omit to same-origin, so we're safe to omit it πŸ˜‰:

Since Aug 25, 2017. The spec changed the default credentials policy to same-origin. Firefox changed since 61.0b13.

Alternatives

If you find yourself doing this many times, you may want to consider a more adavanced libraries like axios or ky which supports global defaults, so that you'll only need to configure the CSRF header once.

Read More