Can't verify CSRF token authenticity Rails/React - ruby-on-rails

I have a react component in my rails app where I'm trying to use fetch() to send a POST to my rails app hosted on localhost, this gives me the error:
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
I'm using devise gem to handle user/registrations and logins.
I have tried to remove protect_from_forgery with: :exception
Here is the code for my fetch,
this.state.ids.sub_id});
fetch(POST_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: body
}).then(res => res.json()).then(console.log);
How can I get the csrf token and send it through the form so that it will pass?
Ideally I would like to just send it through the headers but I have no idea how to access the token.

The simplest way to do this, if you are merely embedding a react component in your Rails view, is to retrieve the csrf token from your rails view and then pass it as a header in your fetch api call.
You can get the csrf token by doing something like this:
const csrf = document.querySelector("meta[name='csrf-token']").getAttribute("content");
And then you just pass it as a header in your fetch call:
...
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrf
},
...
I normally don't use fetch, so not too sure about the exact syntax, but this should help guide you.

Thanks! I ended up with this as a working solution:
In the view that renders my react component
<% csrf_token = form_authenticity_token %>
<%= react_component('ExerciseDisplay', {
program: #program.subprograms.first.exercises, ids: {sub_id: #program.subprograms.first.id, team_id: #team.id, token: csrf_token}
}) %>
I passed the token into state, then accessed it via fetch:
fetch(POST_PATH, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': this.state.ids.token
},
body: body
}).then(res => res.json()).then(console.log);

Inside your form element, add a authenticity_token:
<input type="hidden" name="authenticity_token" value={csrf}/>
Set the value as per the below:
const csrf = document.querySelector("meta[name='csrf-token']").getAttribute("content");

I too faced the same issue when using rails 5.2 and i was able to fix this issue by adding
protect_from_forgery with: :null_session in application_controller.rb i got this from here,please refer this

Related

Sending cookie in XHR response but it's not getting stored?

I'm trying to pass an HttpOnly cookie with a response from an API I'm writing. The purpose is for the cookie act like a refresh token for the purpose of silent refresh for a SPA in React.
In my controller method, I've got the following code:
response.set_cookie(
:foo,
{
value: 'testing',
expires: 7.days.from_now,
path: '/api/v1/auth',
secure: true,
httponly: true
}
)
I'm making a post request to this action with a fetch command like so:
fetch("http://localhost:3001/api/v1/auth", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'aaron#example.com',
password: '123456',
})
})
Not sure if that matters but I'm wondering if passing cookies in a XHR response doesn't work? However, this seems to be working as you can see in my response I'm getting this:
Set-Cookie: foo=testing; path=/api/v1/auth; expires=Sun, 26 Jan 2020 05:15:30 GMT; secure; HttpOnly
Also in the Network tab under Cookies I'm getting this:
However, I'm NOT getting the cookie set under Application -> Cookies:
To clarify, the React app is sitting on localhost:3000 and the rails backend is listening on localhost:3001.
Any ideas?
Ok, so it looks like I needed to configure my CORS (in Rails this is your Rack::CORS middleware.
I setup my config/initializers/cors.rb file like so:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3000'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true
end
end
and my fetch command should look something like this with credentials: 'include' as a parameter:
return fetch(`${endPoint}`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password,
password_confirmation: passwordConfirmation
})
})
Adding credentials: true allows cookies to be set by the browser. Apparently, even if you send them, you need Access-Control-Allow-Credentials: true in the headers for the browser to do anything with them.
EDIT: Recreating this application for learning experience I came across this issue again even after including the credentials option. I wasn't seeing the HttpOnly cookie being stored in the browser. Turns out however, that it WAS and does get sent. You can probably test for this in a controller action. Keep this in mind if this solution doesn't 'seem' to work for you!

CSRF token error occurs when using turbolinks with ruby on rails

A CSRF token error (Can't verify CSRF token authenticity.) will occur if Post transmission is performed on the transition page using turbolinks.
But, when reloading the page, no error occurs.
How can I solve it?
Mr.Mark
Thank you for answering.
Is it due to the fact that the csrf-token of the header is different from the csrf-token of the form?
Why is csrf-token different when using turbolink?
I solved it in the following way, but what do you think?
$(document).on('turbolinks:load', function() {
token = $("meta[name='csrf-token']").attr("content");
$("input[name='authenticity_token']").val(token)
});
I know this is a year old question but this might help someone in need.
The problem here is the csrf-token in meta tag is different from
authenticity_token in the form. I believe turbolinks could be the culprit here.
The solution that worked for me is:
$(document).on('turbolinks:load', function(){ $.rails.refreshCSRFTokens(); });
You will need rails-ujs or jquery-ujs included with your app. If you're on Rails 6, I believe you already have rails-ujs by default.
This is a popular question the last few days...
I'd suggest following:
WARNING: Can't verify CSRF token authenticity rails
Make sure that you have <%= csrf_meta_tag %> in your layout
Add beforeSend to all the ajax request to set the header like below:
$.ajax({ url: 'YOUR URL HERE',
type: 'POST',
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: 'someData=' + someData,
success: function(response) {
$('#someDiv').html(response);
}
});
To send token in all requests you can use:
$.ajaxSetup({
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
});
👋🏻 Rails is expecting the form authenticity_token but the CSRF token from the meta tag is being sent instead because you are POSTing a form with any of this options on the Rails form helper:
turbo: false
local: true
remote: false
I solved it by copying the authenticity token from the meta tag, to the form before submitting the form:
// application.js
function copyCSRFMetaTagToFormAuthenticityToken() {
document.querySelector('input[name="authenticity_token"]').value = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
}
window.copyCSRFMetaTagToFormAuthenticityToken = copyCSRFMetaTagToFormAuthenticityToken
Then in my template:
<%= form_with(url: save_custom_connection_path, method: :post, data: { turbo: false }, :html => { :onsubmit => "window.copyCSRFMetaTagToFormAuthenticityToken()" }) do |form| %>
Notice:
:onsubmit => "window.copyCSRFMetaTagToFormAuthenticityToken()"
I believe this does not represent any security risk because it's using the view's CSRF token anyway.

Rails: session variable is nil outside

I have made method in rails controller which returns json with session variable:
def get_facebook_code
render json: { code: session[:facebook_auth_code] }
end
When I paste link in browser http://localhost:3000/credentials/facebook/code it works fine - session variable returns
And session variable is null when I run request from Postman or from js code from third-party application:
$.ajax({
type: "GET",
url: "http://localhost:3000/credentials/facebook/code",
success: function(msg) {
console.log(msg);
},
});
I allow requests for credentials/* path with help of rack-cors gem
resource '/credentials/*', headers: :any, methods: [:get]
Because by default rails stores sessions in cookies.
Somehow you have added that key 'facebook_auth_code' in your browser session. The same key is not available to postman/third party app requests because cookies are different.

How can I have a Rails API set a JWT in a cookie on a client that is running on another domain?

Long time lurker, first time poster here.
There are many good guides and resources about JWTs and how and where to store them. But I'm running into an impasse when it comes to securely storing and sending a JWT between a ReactJS/Flux app running on a Node server and a completely separate Rails API.
It seems most guides tell you to just store the JWT in local storage and pluck it out for every AJAX request you make and pass it along in a header. https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/ warns against this, however, since local storage is not secure and a malicious person could access that token. It recommends storing it in the cookie instead and just letting the web browser pass it along with each request.
That sounds fine to me since from what I understand cookies get conveniently sent along with every request anyway. It means I can just make AJAX requests from my ReactJS app to my Rails API and have the API pluck it out, check it, and do it's thing.*
The problem I'm running into is my Node application doesn't set a cookie from the response it gets back from the Rails API even though the Rails API (running on localhost:3000) returns a Set-Cookie header and sends it back to the ReactJS/Node app (running on localhost:8080).
Here's my login controller action on my Rails API side:
class V1::SessionsController < ApplicationController
def create
user = User.where(email: params[:user][:email]).first!
if user && user.authenticate(params[:user][:password])
token = issue_new_token_for(user)
# I've tried this too.
# cookies[:access_token] = {
# :value => token,
# :expires => 3.days.from_now,
# :domain => 'https://localhost:8080'
# }
response.headers['Set-Cookie'] = "access_token=#{token}"
render json: { user: { id: user.id, email: user.email }, token: token }, status: 200
else
render json: { errors: 'username or password did not match' }, status: 422
end
end
end
The gist of it is it takes an email and password, looks the user up, and generates JWT if the info checks out.
Here's the AJAX request that is calling it from my Node app:
$.ajax({
url: 'http://localhost:3000/v1/login',
method: 'post',
dataType: 'json',
data: {
user: {
email: data.email,
password: data.password
},
callback: '' //required to get around ajax CORS
},
success: function(response){
console.log(response);
},
error: function(response) {
console.log(response);
}
})
Inspecting the response from the Rails API shows it has a Set-Cookie header with a value of access_token=jwt.token.here
Screenshot:
Chrome Dev Tools Inspector Screenshot
However, localhost:8080 does not show any cookies set and subsequent AJAX calls from my Node/React app do not have any cookies being sent along with them.
My question is, what piece(s) am I misunderstanding. What would I have to do to make storing JWTs in cookies work in this scenario?
A follow-up question: assuming storing the JWT in a cookie is not an option, what potential security risks could there be with storing the JWT in local storage (assuming I don't put any sensitive info in the JWT and they all expire in some arbitrary amount of time)?
*this may be a fundamental misunderstanding I have. Please set me straight if I have this wrong.
Side-notes that may be of interest:
My Rails API has CORS setup to only allow traffic from localhost:8080
in development.
In production, the Node/React app will probably be
running on a main domain (example.com) and the Rails API will be
running on a sub domain (api.example.com), but I haven't gotten that
far yet.
There's nothing sensitive in my JWT, so local storage is an
option, but I want to know why my setup doesn't work with cookies.
Update elithrar submitted an answer that worked:
I needed to modify my AJAX request with xhrFields and crossDomain as well as tell jQuery to support cors:
$.support.cors = true;
$.ajax({
url: 'http://localhost:3000/v1/login',
method: 'post',
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
data: {
user: {
email: data.email,
password: data.password
}
},
success: function(response){
console.log(response);
},
error: function(response) {
console.log(response);
}
})
And I added credentials: true and expose: true to my Rack Cors configuration on my Rails API (the * is only for my development environment):
config.middleware.insert_before 0, 'Rack::Cors' do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :path, :options], credentials: true, expose: true
end
end

WARNING: Can't verify CSRF token authenticity rails

I am sending data from view to controller with AJAXand I got this error:
WARNING: Can't verify CSRF token authenticity
I think I have to send this token with data.
Does anyone know how can I do this ?
Edit: My solution
I did this by putting the following code inside the AJAX post:
headers: {
'X-Transaction': 'POST Example',
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
You should do this:
Make sure that you have <%= csrf_meta_tag %> in your layout
Add beforeSend to all the ajax request to set the header like below:
$.ajax({ url: 'YOUR URL HERE',
type: 'POST',
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: 'someData=' + someData,
success: function(response) {
$('#someDiv').html(response);
}
});
To send token in all requests you can use:
$.ajaxSetup({
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
});
The best way to do this is actually just use <%= form_authenticity_token.to_s %> to print out the token directly in your rails code. You dont need to use javascript to search the dom for the csrf token as other posts mention. just add the headers option as below;
$.ajax({
type: 'post',
data: $(this).sortable('serialize'),
headers: {
'X-CSRF-Token': '<%= form_authenticity_token.to_s %>'
},
complete: function(request){},
url: "<%= sort_widget_images_path(#widget) %>"
})
If I remember correctly, you have to add the following code to your form, to get rid of this problem:
<%= token_tag(nil) %>
Don't forget the parameter.
Indeed simplest way. Don't bother with changing the headers.
Make sure you have:
<%= csrf_meta_tag %> in your layouts/application.html.erb
Just do a hidden input field like so:
<input name="authenticity_token"
type="hidden"
value="<%= form_authenticity_token %>"/>
Or if you want a jQuery ajax post:
$.ajax({
type: 'POST',
url: "<%= someregistration_path %>",
data: { "firstname": "text_data_1", "last_name": "text_data2", "authenticity_token": "<%= form_authenticity_token %>" },
error: function( xhr ){
alert("ERROR ON SUBMIT");
},
success: function( data ){
//data response can contain what we want here...
console.log("SUCCESS, data="+data);
}
});
Ugrading from an older app to rails 3.1, including the csrf meta tag is still not solving it. On the rubyonrails.org blog, they give some upgrade tips, and specifically this line of jquery which should go in the head section of your layout:
$(document).ajaxSend(function(e, xhr, options) {
var token = $("meta[name='csrf-token']").attr("content");
xhr.setRequestHeader("X-CSRF-Token", token);
});
taken from this blog post: http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails.
In my case, the session was being reset upon each ajax request. Adding the above code solved that issue.
Make sure that you have <%= csrf_meta_tag %> in your layout
Add a beforeSend to include the csrf-token in the ajax request to set the header. This is only required for post requests.
The code to read the csrf-token is available in the rails/jquery-ujs, so imho it is easiest to just use that, as follows:
$.ajax({
url: url,
method: 'post',
beforeSend: $.rails.CSRFProtection,
data: {
// ...
}
})
The top voted answers here are correct but will not work if you are performing cross-domain requests because the session will not be available unless you explicitly tell jQuery to pass the session cookie. Here's how to do that:
$.ajax({
url: url,
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
},
xhrFields: {
withCredentials: true
}
});
I just thought I'd link this here as the article has most of the answer you're looking for and it's also very interesting
http://www.kalzumeus.com/2011/11/17/i-saw-an-extremely-subtle-bug-today-and-i-just-have-to-tell-someone/
You can write it globally like below.
Normal JS:
$(function(){
$('#loader').hide()
$(document).ajaxStart(function() {
$('#loader').show();
})
$(document).ajaxError(function() {
alert("Something went wrong...")
$('#loader').hide();
})
$(document).ajaxStop(function() {
$('#loader').hide();
});
$.ajaxSetup({
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))}
});
});
Coffee Script:
$('#loader').hide()
$(document).ajaxStart ->
$('#loader').show()
$(document).ajaxError ->
alert("Something went wrong...")
$('#loader').hide()
$(document).ajaxStop ->
$('#loader').hide()
$.ajaxSetup {
beforeSend: (xhr) ->
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
}
If you are not using jQuery and using something like fetch API for requests you can use the following to get the csrf-token:
document.querySelector('meta[name="csrf-token"]').getAttribute('content')
fetch('/users', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')},
credentials: 'same-origin',
body: JSON.stringify( { id: 1, name: 'some user' } )
})
.then(function(data) {
console.log('request succeeded with JSON response', data)
}).catch(function(error) {
console.log('request failed', error)
})
oops..
I missed the following line in my application.js
//= require jquery_ujs
I replaced it and its working..
======= UPDATED =========
After 5 years, I am back with Same error, now I have brand new Rails 5.1.6, and I found this post again. Just like circle of life.
Now what was the issue is:
Rails 5.1 removed support for jquery and jquery_ujs by default, and added
//= require rails-ujs in application.js
It does the following things:
force confirmation dialogs for various actions;
make non-GET requests from hyperlinks;
make forms or hyperlinks submit data asynchronously with Ajax;
have submit buttons become automatically disabled on form submit to prevent double-clicking.
(from: https://github.com/rails/rails-ujs/tree/master)
But why is it not including the csrf token for ajax request? If anyone know about this in detail just comment me. I appreciate that.
Anyway I added the following in my custom js file to make it work (Thanks for other answers to help me reach this code):
$( document ).ready(function() {
$.ajaxSetup({
headers: {
'X-CSRF-Token': Rails.csrfToken()
}
});
----
----
});
Use jquery.csrf (https://github.com/swordray/jquery.csrf).
Rails 5.1 or later
$ yarn add jquery.csrf
//= require jquery.csrf
Rails 5.0 or before
source 'https://rails-assets.org' do
gem 'rails-assets-jquery.csrf'
end
//= require jquery.csrf
Source code
(function($) {
$(document).ajaxSend(function(e, xhr, options) {
var token = $('meta[name="csrf-token"]').attr('content');
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
});
})(jQuery);
If you're using javascript with jQuery to generate the token in your form, this works:
<input name="authenticity_token"
type="hidden"
value="<%= $('meta[name=csrf-token]').attr('content') %>" />
Obviously, you need to have the <%= csrf_meta_tag %> in your Ruby layout.
I struggled with this issue for days. Any GET call was working correctly, but all PUTs would generate a "Can't verify CSRF token authenticity" error. My website was working fine until I had added a SSL cert to nginx.
I finally stumbled on this missing line in my nginx settings:
location #puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Forwarded-Proto https; # Needed to avoid 'WARNING: Can't verify CSRF token authenticity'
proxy_pass http://puma;
}
After adding the missing line "proxy_set_header X-Forwarded-Proto https;", all my CSRF token errors quit.
Hopefully this helps someone else who also is beating their head against a wall. haha
For those of you that do need a non jQuery answer you can simple add the following:
xmlhttp.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
A very simple example can be sen here:
xmlhttp.open("POST","example.html",true);
xmlhttp.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
xmlhttp.send();
if someone needs help related with Uploadify and Rails 3.2 (like me when I googled this post), this sample app may be helpful:
https://github.com/n0ne/Uploadify-Carrierwave-Rails-3.2.3/blob/master/app/views/pictures/index.html.erb
also check the controller solution in this app
I'm using Rails 4.2.4 and couldn't work out why I was getting:
Can't verify CSRF token authenticity
I have in the layout:
<%= csrf_meta_tags %>
In the controller:
protect_from_forgery with: :exception
Invoking tcpdump -A -s 999 -i lo port 3000 was showing the header being set ( despite not needing to set the headers with ajaxSetup - it was done already):
X-CSRF-Token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
DNT: 1
Content-Length: 125
authenticity_token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
In the end it was failing because I had cookies switched off. CSRF doesn't work without cookies being enabled, so this is another possible cause if you're seeing this error.

Resources