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.

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

kinopyo avatar
Written By

kinopyo

Indoor enthusiast, web developer, and former hardcore RTS gamer. #parenting
Published in Rails
Enjoyed the post?

Clap to support the author, help others find it, and make your opinion count.