DEV Community

Liz Laffitte
Liz Laffitte

Posted on

Resolving the ActionController::InvalidAuthenticityToken Error in a Rails/React App

You've just started building a Rails (backend) and React (frontend) application. You've configured Webpack. You've set up your database. You've connected your backend and your frontend.

You've built out your first model, controller and view. You go to test your first POST request and it happens...

ActionController::InvalidAuthenticityToken
Enter fullscreen mode Exit fullscreen mode

and/or

Can't verify CSRF token authenticity.
Enter fullscreen mode Exit fullscreen mode

You Google. You weep. You half-heartedly try to use rack-cors.

Never fear! Here is the simple solution:

1. Take Note of application.html.erb

First open /app/views/layouts/application.html.erb. This view should've been generated when you ran the $rails new command.

You should see this tag: <%= csrf_meta_tags %>

What the heck are these, you say? From the Ruby docs:

These are used to generate the dynamic forms that implement non-remote links with :method.

For AJAX requests other than GETs, extract the “csrf-token” from the meta-tag and send as the “X-CSRF-Token” HTTP header.

2. Grab the CSRF Token in Relevant Component

For me, the offending controller action was user#create, meaning the relevant 'view' component was my signup form.

I added a line of JavaScript to my component to capture the token:
/app/javascript/components/Signup.jsx

const token = document.querySelector('meta[name="csrf-token"]').content;
Enter fullscreen mode Exit fullscreen mode

Here we are querying the DOM, finding the meta tag with the name value "csrf-token", and assigning it to the variable token.

Pass the Token on Submit

Then I passed the new variable to my signup function (passed as a prop from the parent component), so that my signup action would have access to it.

/app/javascript/components/Signup.jsx

const handleOnSubmit = (e) => {
    e.preventDefault()
    signup({username, email, password}, token)
}
Enter fullscreen mode Exit fullscreen mode

This won't work yet, since we haven't altered our signup action to accept the token.

Accept the Token in Your Action

Back in my userActions, we make a few tweaks to the signup action.
/app/javascript/actions/userActions.js

export const signup = (credentials, token) => {
    return dispatch => {
        return fetch('http://localhost:3000/signup', {
            credentials: 'include',
            method: "POST",
            headers: {
                "X-CSRF-Token": token,
              "Content-Type": "application/json"
            },
            body: JSON.stringify(credentials)
          })
        .then(response => response.text())
        .then(userData => {
            if(userData.error){
                console.log(userData.errors)
            } else {
                dispatch(addUser(userData.data))
            }

        })
        .catch(console.log())
    }
}
Enter fullscreen mode Exit fullscreen mode

First we change signup() to accept a second parameter: token.
export const signup = (credentials, token) => {

Then we pass the token under headers.

            headers: {
                "X-CSRF-Token": token,
              "Content-Type": "application/json"
            },
Enter fullscreen mode Exit fullscreen mode

And... that's it! We're now able to successfully complete the POST request to our backend.

To recap:

  • Use JS to capture the token, outputted by application.html.erb, in the component where the POST happens: /app/javascript/components/Signup.jsx
const token = document.querySelector('meta[name="csrf-token"]').content;
Enter fullscreen mode Exit fullscreen mode
  • Send the token to your POST action on submit: /app/javascript/components/Signup.jsx
const handleOnSubmit = (e) => {
    e.preventDefault()
    signup({username, email, password}, token)
}
Enter fullscreen mode Exit fullscreen mode
  • Send the token to your backend, in your POST action: /app/javascript/actions/userActions.js
export const signup = (credentials, token) => {
    return dispatch => {
        return fetch('http://localhost:3000/signup', {
            credentials: 'include',
            method: "POST",
            headers: {
                "X-CSRF-Token": token,
              "Content-Type": "application/json"
            },
            body: JSON.stringify(credentials)
          })
       ....
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
tfantina profile image
Travis Fantina

Thank you so much! Really helped out when trying to implement Stripe!