Rails freaks out if you don't have the csrf token in a hidden input on a form.
But If I server render the form, I have no way to get the csrf token and have to basically update it once reconcilled on the front end via a lookup to the meta tag.
This causes react to throw a warning that we are losing benefits of the server rendering.
We should find some idiomatic way of grabbing the csrf token on the server.
You can render it with a method that's something like <%= form_authentication_token => and pass it to your form however you want. Doing this idiomatically might be a tall order right now. I have a way of doing this in my project right now but will have to look it up in the morning.
Eric
On Mar 13, 2015, at 00:08, Blaine Kasten [email protected] wrote:
Rails freaks out if you don't have the csrf token in a hidden input on a form. But If I server render the form, I have no way to get the csrf token and have to basically update it once reconcilled on the front end via a lookup to the meta tag. This causes react to throw a warning that we are losing benefits of the server rendering. We should find some idiomatic way of grabbing the csrf token on the server.
—
Reply to this email directly or view it on GitHub.
@ericdfields That seems like a reasonable approach. Thanks for that tip. I could see it being a tall order, so I understand the feeling to hold off on it. I would say releasing v1 is probably a bigger priority.
@blainekasten the method you want is <%= form_authenticity_token %>
I would like to see an approach for building reactive forms with rails and accounting for csrf. I'm building up a form with newforms and am getting happier with the results, but I have to munge up the input names client-side on submit in order to properly format them into the hash rails wants. In the future i hope to have a more elegant solution that allows for all those good server-side benefits. Maybe I can get around to making a gem…
:+1: This would be great. :smiley:
Do you think we could take advantage of the context feature? maybe something like, if a component has a:
MyComponent.contextTypes = {
csrfToken: React.PropTypes.string.isRequired
}
Then, that would be available on the context through the gem? Since the gem would have access to the ruby method form_authenticity_token
Opening this back up, I have a suggestion:
the react_component view function take in a csrf_token field, that uses the default value of form_authenticity_token, so the actual function is therefore:
react_component(csrf_token: form_authenticity_token, component_name, props)
Additionally, you could now always have access to the token via some attribute, or have the react-rails gem come with a component by default, such as <RailsForm> which includes this csrf_token argument. Thoughts?
Another approach is to use the helper csrf_meta_tags which can be rendered within the layout and then pulled from within a React component. For example, here is my React component which achieves that:
var FormCsrfInput = React.createClass({
render() {
const token = $('meta[name="csrf-token"]').attr('content');
return (
<input type="hidden" name="authenticity_token" value={token} readOnly={true} />
)
}
});
and then used within a dynamic React form.
Opening this back up again, another approach/hack I'm working with is getting react-rails to inject a CSRF token to every component via the controller render method:
def call(component_name, options, &block)
props = options.fetch(:props, {}).reverse_merge({
csrf_token: controller.send(:form_authenticity_token)
})
options = options.slice(:data, :aria, :tag, :class, :id).merge(prerender: true)
react_component(component_name, props, options, &block)
end
See https://github.com/reactjs/react-rails/commit/042e943eb3a4a861298481e7c33c9da5f2e86cc3
I'm sure there's a tidier way to do it, but this could be a starting point for this and rendering from within a view.
Thanks for sharing your solutions! I don't think there's anything that needs changing gem-wise so I'm going to close this.
To expand on @acconrad's answer, I included the Rails helper csrf_token: form_authentication_token as a _prop_ on the component itself, rather than a separate entity on the Component; so:
<%= react_component("ComponentName", { prop: prop1, csrf_token: form_authenticity_token }) %>
And this worked nicely.
@dsuare1 doesn't this open up a vulnerability to those inspecting your site with React- they will be able to grab the csrf token from the React chrome extension.
@abhishekover9000 The CSRF token is shown on every rails page anyway. Search the source of this page for 'authenticity-token'. This is not a security vulnerability as the purpose of this (simplified for any sticklers in the audience) is to ensure that the page has been loaded in HTML before posting data, so that people can't just post from another website as a trick.
Good catch @abhishekover9000, and thanks for the clarification @BookOfGreg; since posting this, I've actually gone in a different direction for getting the proper token in there; like many, I'm using axios to carry out my API calls. I set a common header for all axios requests to include the csrf-token; this approach has been working nicely for me thus far.
const csrfToken = document.querySelector('[name="csrf-token"]').content;
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken;
@BookOfGreg @dsuare1 ah i see. i was going to expose a rails controller route to GET the CSRF token and call that from React prior to the main request but since it's not a security risk no reason to make the app more chatty.
In Rails 5 they added further complexity to the auth tokens and the var passing in from the component does not match the one in the meta tag, so it's definitely good to use the meta tag selector to get the token. If using fetch must add credentials: "same-origin", as well as described here- https://blog.codeship.com/level-up-your-security-in-rails/
Most helpful comment
Good catch @abhishekover9000, and thanks for the clarification @BookOfGreg; since posting this, I've actually gone in a different direction for getting the proper token in there; like many, I'm using axios to carry out my API calls. I set a common header for all axios requests to include the csrf-token; this approach has been working nicely for me thus far.