WARNING: Can't verify CSRF token authenticity - ruby-on-rails

I'm having trouble making a cross-domain request from my shopify site to my rails app, which is installed as a shopify app. The problem, as stated in the title, is that my server warns me that it Can't verify CSRF token authenticity I'm making the request from a form returned by my rails app, which includes the relevant CSRF token. The request is done with jQuery's ajax method, and the preflight OPTIONS request is being handled by rack-cors.
I've included the X-CSRF-Token in my headers as was suggested in this answer. My post request is being made from a form, so my question is not answered here. The options request (mentioned in this question) is indeed being handled, as I just confirmed by asking this question. I've been stuck on this for a while, and have done a bit of reading.
I'm going to try walking through the process code-snippet by code-snippet, and maybe by the time I finish writing this post I will have discovered the answer to my problem (if that happens, then you won't ever get a chance to read this paragraph).
Here are the new and create methods from my controller.
class AustraliaPostApiConnectionsController < ApplicationController
# GET /australia_post_api_connections/new
# GET /australia_post_api_connections/new.json
def new
# initializing variables
respond_to do |format|
puts "---------------About to format--------------------"
format.html { render layout: false } # new.html.erb
format.json { render json: #australia_post_api_connection }
end
end
# POST /australia_post_api_connections
# POST /australia_post_api_connections.json
def create
#australia_post_api_connection = AustraliaPostApiConnection.new(params[:australia_post_api_connection])
respond_to do |format|
if #australia_post_api_connection.save
format.js { render layout: false }
else
format.js { render layout: false }
end
end
end
end
(I wonder about the respond_to block in the create method, but I don't think that would cause the CSRF token to fail verification.)
Within my app, at /AUSController/index, I have an ajaxified GET request that brings back the form from /AUSController/new. My goal is to be able to make all the same calls from a cross-domain origin as I can from within my app. Right now the GET request works for both, and so I will neglect to include the 'new' form. When the HTML is finally rendered, the form element has the following:
<form method="post" id="new_australia_post_api_connection" data-remote="true" class="new_australia_post_api_connection" action="http://localhost:3000/australia_post_api_connections" accept-charset="UTF-8">
<!-- a bunch more fields here -->
<div class="field hidden">
<input type="hidden" value="the_csrf_token" name="authenticity_token" id="tokentag">
</div>
</div>
</div>
</form>
The CSRF token is generated by a call to form_authenticity_token as detailed in one of the references mentioned above.
The next step is done differently in the two cases:
My app successfully returns the new form to the shop upon an ajax request. I've tested this within the app, that is by making an ajax call to /controller/new from /controller/index, and then submitting the form. This works like a charm. The js that is returned from a successful POST within my app is as follows:
/ this is rendered when someone hits "calculate" and whenever the country select changes
:plain
$("#shipping-prices").html("#{escape_javascript(render(:partial => 'calculations', :object => #australia_post_api_connection))}")
Which renders the following partial,
= form_tag "/shipping_calculations", :method => "get" do
= label_tag :shipping_type
%br
- #service_list.each_with_index do |service, index|
- checked = true if index == 0
= radio_button_tag(:shipping_type, service[:code], checked)
= label_tag(:"shipping_type_#{service[:code]}", service[:name])
= " -- $#{service[:price]}"
%br
When I call it from the same domain, request.header contains the following:
HTTP_X_CSRF_TOKEN
the_token_I_expect=
rack.session
{
"session_id"=>"db90f199f65554c70a6922d3bd2b7e61",
"return_to"=>"/",
"_csrf_token"=>"the_token_I_expect=",
"shopify"=>#<ShopifyAPI::Session:0x000000063083c8 #url="some-shop.myshopify.com", #token="some_token">
}
And the HTML is rendered and displayed nicely.
From the cross domain source, however, things are understandibly more complicated. This is where CORS and CSRF tokens and routes and all these little details start creeping in. In particular, when I make the ajax call I use the following script (which does not live in my rails app, it lives on the cross-domain server). The action of this ajax request is attached to the submit button by the callback function from the GET request, and I've included the GET request for the sake of completion.
<script>
var host = "http://localhost:3000/"
var action = "australia_post_api_connections"
console.log("start")
$.ajax({
url: host + action,
type: "GET",
data: { weight: 20 },
crossDomain: true,
xhrFields: {
withCredentials: true
},
success: function(data) {
console.log("success");
$('#shipping-calculator').html(data);
$('#new_australia_post_api_connection')
.attr("action", host + action);
$('.error').hide();
$(".actions > input").click(function() {
console.log("click")
// validate and process form here
$('.error').hide();
var to_postcode = $("input#australia_post_api_connection_to_postcode").val();
// client side validation
if (to_postcode === "") {
$("#postcode > .error").show();
$("input#australia_post_api_connection_to_postcode").focus();
return false;
}
tokentag = $('#tokentag').val()
var dataHash = {
to_postcode: to_postcode,
authenticity_token: tokentag // included based on an SO answer
}
// included based on an SO answer
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-TOKEN', tokentag);
}
});
$.ajax({
type: "POST",
url: host + action,
data: dataHash,
success: function(data) {
$('#shipping-prices').html(data);
}
}).fail(function() { console.log("fail") })
.always(function() { console.log("always") })
.complete(function() { console.log("complete") });
return false;
});
}
}).fail(function() { console.log("fail") })
.always(function() { console.log("always") })
.complete(function() { console.log("complete") });
$(function() {
});
</script>
However, when I call it from this remote location (the distant slopes of Shopify), I find the following in my request headers,
HTTP_X_CSRF_TOKEN
the_token_I_expect=
rack.session
{ }
And I receive a very unpleasant NetworkError: 500 Internal Server Error rather than the 200 OK! that I would like... On the server side we find the logs complaining that,
Started POST "/australia_post_api_connections" for 127.0.0.1 at 2013-01-08 19:20:25 -0800
Processing by AustraliaPostApiConnectionsController#create as */*
Parameters: {"weight"=>"20", "to_postcode"=>"3000", "from_postcode"=>"3000", "country_code"=>"AUS", "height"=>"16", "width"=>"16", "length"=>"16", "authenticity_token"=>"the_token_I_expect="}
WARNING: Can't verify CSRF token authenticity
Completed 500 Internal Server Error in 6350ms
AustraliaPostApiConnection::InvalidError (["From postcode can't be blank", "The following errors were returned by the Australia Post API", "Please enter Country code.", "Length can't be blank", "Length is not a number", "Height can't be blank", "Height is not a number", "Width can't be blank", "Width is not a number", "Weight can't be blank", "Weight is not a number"]):
app/models/australia_post_api_connection.rb:78:in `save'
The lack of a rack.session seems suspicious like the cause of my misery... but I haven't been able to find a satisfying answer.
Finally I have seen fit to include my rack-cors setup, in case it is useful.
# configuration for allowing some servers to access the aus api connection
config.middleware.use Rack::Cors do
allow do
origins 'some-shop.myshopify.com'
resource '/australia_post_api_connections',
:headers => ['Origin', 'Accept', 'Content-Type', 'X-CSRF-Token'],
:methods => [:get, :post]
end
end
Thank you so much for even reading all of this. I hope the answer has to do with that empty rack.session. That would be satisfying, at least.

Well one of my coworkers figured it out. The problem was, the has I was sending didn't have the same structure as the hash I was expecting in my controller.
In my controller I instantiate a new API connection as follows,
AustraliaPostApiConnection.new(params[:australia_post_api_connection])
I am looking for params[:australia_post_api_connection], but there is no such index in my data hash, which looks like,
var dataHash = {
to_postcode: to_postcode,
authenticity_token: tokentag // included based on an SO answer
}
To fix this I changed the JS file to contain,
var dataHash = {
to_postcode: to_postcode,
}
var params = {
australia_post_api_connection: dataHash,
authenticity_token: tokentag // included based on an SO answer
}
And now it works! Thanks co-worker!

Related

Ruby on Rails: How to Permit parameters with "_json"

I am trying to get POST requests to work coming from a React.js application to a Ruby on Rails API.
The parameters are:
Parameters: {"_json"=>"{'Name': 'ExampleSurvey', 'Draft_Status': 'true', 'Active_Status': 'false' }", "survey"=>{}}
My survey_params method is:
def survey_params
params.permit(:Name, :Draft_Status, :Active_Status)
end
My API Call from React is:
const post = (endpoint, body) => {
const url = ANAMNESIS_CONFIG.backend.location + endpoint ?? '';
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Anamnesis-Secret': ANAMNESIS_CONFIG.backend.secret
},
body: JSON.stringify(body)
}).then(response => response.json())
};
const submitHandler = e => {
e.preventDefault();
console.log({ surveyDetails })
post('survey',`{'Name': '${surveyDetails.name}', 'Draft_Status': 'true', 'Active_Status': 'false' }`)
}
The following Curl Request allows me to enter new surveys without any issues:
curl -X POST -d "Name=Example&Draft_Status=true&Active_Status=false" http://localhost:3000/survey
How can I edit Rails or React to get the Post request to work properly in a way that allows the curl request to still work?
Update:
Here is a picture from the logs: The first request is from Curl. The second request is from React. Hope this helps make the error more clear. Thanks for the help so far.
[
Changed the POST body from a string to a JSON object. This caused Rails to accept the params.
post('survey',{Name: surveyDetails.name, Draft_Status: true, Active_Status: false})
Shoutout to max (https://stackoverflow.com/users/544825/max) for coming up with solution!
In your controller you might want to skip forgery protection for json requests. It would be useful if you'd share a part of your logs where POST from React happens, otherwise it's hard to tell what the reason could be. This works when you get Can't verify CSRF token authenticity for your POST requests.
class ApplicationController < ActionController::Base
protect_from_forgery unless: -> { request.format.json? }
end

Rails use of httparty to parse the JSON to external URL

In a Rails application, a user has to be redirected to an external site for external processing. The controller generates a valid JSON string and a set of headers:
#result = HTTParty.post(
"https://some-test.supertester.com/checkout",
:verify => false,
:body => JSON.parse(#payment).to_json,
headers: {
'Content-Type' => 'application/json',
'X-TimeStamp' => "#{Time.now.to_i}",
'X-API-ID:' => "#{#api_id}",
'X-API-Signature:' => "#{#api_signature}"
},
timeout: 20
)
This action is launched by the user via:
<%= link_to t('proceed'), data_url_cart_path(id: #existing_cart.id), class: 'button' %>
whose controller action generates the above JSON string and call.
However, Rails tries to respond to this action with a view with the same action's name (even generating a blank with format.json { head :no_content }), when the goal is to redirect the user to the aforementioned URL with the defined headers and payload.
How can this be achieved?
HTTParty is making a network request with a json payload. In this situation #response will be the response from the network request. If you call puts #response.parsed_response.inspect you will see the json returned with said response in the terminal window running your Rails server.
I think you may not need to redirect the user to an external site at all, but instead check the response to see if the action you were trying to make was successful and handle it in your own application.

Responding to AJAX Rails request with .js.erb and Rails instance variable

I am struggling w/ JS AJAX requests in Rails. There is an official guide here, but I am having slight difficulties matching it with ES6 JS. I am having troubles passing things back to my frontend after making my requests.
I have a JS window.onload call made, because I am trying to find the user’s screen size (among other things) and pass it back to Rails:
let xhttp = new XMLHttpRequest();
const url = "/users";
xhttp.open("POST", url);
// Some other things added to it...
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 201) {
console.log(this.responseText);
}
};
xhttp.send(JSON.stringify({user_info: userInfo}));
It is posting to /users some information about the session. This is going through fine. Note the console.log that keeps track of the response, we will get to this later.
In my Rails controller:
def create
user_info = params[:user_info].permit!
user_info = user_info.to_s
#fingerprint_user = User.find_or_create_by(fingerprint: user_info)
respond_to do |format|
# NOTE: I have tried a few things here
# format.html { redirect_to #fingerprint_user, notice: "Successfully identified user by fingerprint." }
# format.js
format.json { render json: #fingerprint_user, status: :created, head: :ok }
end
end
The JSON sender is working correctly. The console.log in the JS above correctly console.logs the received JSON. The request responds with 201, and the #fingerprint_user instance variable in JSON form.
My problem is with returning ERB JS with the instance variable. As shown in the guide, I have tried adding format.js. Then, the request returns a 200, and the contents of my views/users/create.js.erb file:
console.log("hello");
However, it is not actually logging to console.
Lastly, I tried with all format fields (js, html, and json). Here is my show.html.erb:
<p>Got user: <%= #fingerprint_user.to_s %> </p>
Here is a better views/users/create.js.erb file, where fingerprint is a div in my index.html.erb:
console.log("hello");
$("<%= escape_javascript(render #fingerprint_user) %>").appendTo("#fingerprint");
Once again, the response is 200, and the appropriate html, but this is not rendered on the page.
Doing requests for AJAX requests for JavaScript is different then requesting JSON. Instead of requesting some data and parsing it you actually load the data and then eval it into the current page context through various tricks like appending script tags into the document. This is the actual Rails UJS implementation:
processResponse = (response, type) ->
if typeof response is 'string' and typeof type is 'string'
if type.match(/\bjson\b/)
try response = JSON.parse(response)
else if type.match(/\b(?:java|ecma)script\b/)
script = document.createElement('script')
script.setAttribute('nonce', cspNonce())
script.text = response
document.head.appendChild(script).parentNode.removeChild(script)
else if type.match(/\b(xml|html|svg)\b/)
parser = new DOMParser()
type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'
try response = parser.parseFromString(response, type)
response
This is basically how we used to do AJAX calls cross domain ten years ago with JSONP to get around the limitations of the browsers of the day.
You can emulate the same thing in a "raw ajax request" with:
let xhttp = new XMLHttpRequest();
const url = "/users";
xhttp.open("POST", url);
// Some other things added to it...
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 201) {
let script = document.createElement('script');
script.innerHTML = data;
document.querySelector('head').appendChild(script);
}
};
But quite frankly js.erb is a horrible idea. It makes an absolute mess out of the server and client side responibilities and makes your code very difficult to follow and reason about and it moves JS out of the assets/webpack pipeline and into a smattering of proceedural junk script views. The only possible reason to use it is how lazy you can be with Rails UJS and still add some ajax to your application.
If you're writing an ajax handler anyways just return a chunk of html (in a json object or as html) and append it to the DOM instead.

Is this a good way to do AJAX request in Rails 4?

I am mostly familiar with a full JavaScript stack and ever since I have been working in Ruby on Rails, I was never fully confident with the way I make XHR requests, hence, I want to validate my understanding. I looked at the following questions
Ajax request throws 500 error, but the request works
Processing after the request was completed
Rails 3: How to "redirect_to" in Ajax call?
but didn't quite answer my issue.
Here is what is happening from my understanding. In Rails we have our MVC, and the Controller is essentially where we control what the route will render. It can also be designed as an API, for doing things like CRUD.
Next, on our front end, for the sake of simplicity let's use jQuery. We want to make an AJAX request and handle the response.
$.ajax({
url: '/new-sandwich/'+food_id,
type: 'post',
data: { food_id: food_id}
})
.done(function() {
// if it works, refresh the page
})
.fail(function(e) {
// if it works alert the user and refresh the page
alert(e.responseText)
return location.reload();
})
Suppose the Controller for this is very thick, so the response takes up to a couple seconds.
After going through about 100 lines or so... the problem then happens with the response.
If it fails, it alerts the user and after refresh it still processes as if everything was okay. To fix that, I believe I have to add better validation at the top of the controller action.
Next, the problem is that since this a post request, I don't can't figure out where to REDIRECT? If the request is succesful, I want it refresh, but do I do that from the client or the server? If I don't add a render :nothing => true at the end of the POST Controller Action I get...
ActionView::MissingTemplate - Missing template sandwich/create_sandwich, application/create_sandwich
but if I do, then is there a point of doing it in the JavaScript?
Well, in my controller, I typically do something along the lines of:
class FooController < ApplicationController
def create
... do some stuff
render partial: 'some_partial', locals: {if: :needed}
end
end
And then in the javascript, something like:
$.ajax({
url: '/new-sandwich/'+food_id,
type: 'post',
data: { food_id: food_id}
})
.success(function(data) {
$('#someSelector').html(data)
})
.fail(function(e) {
// if it works alert the user and refresh the page
alert(e.responseText)
return location.reload();
})
So, the controller returns some HTML and the js inserts that HTML into the page in the location defined by $('#someSelector').
If my controller method really takes a lot of time, then I would typically do something along the lines of:
class FooController < ApplicationController
def create
render json: {job_id: FooCreateService.call(some: params[:thing])}, status: :ok
end
end
And then my ajax call might look something like:
$.ajax({
url: '/new-sandwich/'+food_id,
type: 'post',
data: { food_id: food_id}
})
.done(function(data) {
#.$jobId = data['job_id']
#showAWorkingOnItNotification()
#pollForResults()
})
.fail(function(e) {
// if it works alert the user and refresh the page
alert(e.responseText)
return location.reload();
})
My #pollForResults function would use #.$jobId to poll a background job service for completion, and then respond appropriately when the job is done (successfully or unsuccessfully).
It is, naturally, a bit more complicated than all of that. But, that's the direction I usually head.

Creating a :remote=>true post using AJAX rather than rails

I've spent a day spinning my wheels trying to understand how Rails :remote=>true works. The question below seems complicated but I'm trying to understand this simple question with the information I provide:
How can I make an Ajax call that simply renders as JS without using :remote=>true?
From my understanding :remote=>true simply generates and handles an AJAX call:
My view looks like this:
opts in a very complicated way, creates a link with :remote => true. This is omitted for simplicity
.e_list
= opts.sort_link(:name)
= opts.sort_link(:description)
- e_list.each do |e|
.entry
= link_to(e.name, '#', class: 'select_e', data: {e_id: e.id})
= e.description
= paginate e_list, remote: true, params: {search:"j", com_id: com.id}
Gallery.js.erb
$('#com<%= #com.id %>').replaceWith('<%= escape_javascript render(partial: "shared/com", locals: {com: #com}) %>');
My Controller:
def gallery
if params[:com_id]
#com = #s.com.find(params[:com_id])
#com.filter = params
end
if c = #s.com.where(:_type => "Com").first
#current_e = c.entries(#user.app_id).first
#current_e.og_url = view_context.og_url(#current_e)
end
render :text => "foobar" if !#current_e
end
logs, after the user clicks on the pagination links or sort links (the key is those links have :remote => true)
Started GET "super long url" for 127.0.0.1 at 2012-05-04 16:08:42 -0700
Processing by CController#gallery as JS
SO I TRY TO RECREATE THIS WITH AJAX:
$('button.search').live 'click', (e) ->
search = $(e.target).attr('search-term')
success_callback = (results) ->
if results
console.log(results)
update_components(results[0].entry, '.entry')
else
$.ajax(
url: 'super long url that is exactly the same url as above!'
).done ->
return false
MY FAILED RESPONSE THAT DOES NOT RENDER AS JS, YET I THOUGHT :remote => true was simply an ajax call wtf?:
Started GET "super long url identical as the one that renders JS" for 127.0.0.1 at 2012-05-04 16:07:22 -0700
Processing by ContestController#gallery as */*
What is going on? How can I make an Ajax call that simply renders as JS without using :remote=>true?
Try
$.ajax({
url: 'your url',
dataType: 'script'
})
http://www.alfajango.com/blog/rails-3-remote-links-and-forms-data-type-with-jquery/
try
/screens/4fa02763dc1c82269c0001da/contest/gallery.js?app_row_id=5....
If you want the js from the response to execute in the browser you should need to do something like eval(response), but i'm just suggesting, I never done it and even know how to eval code of a string in javascript.
You could use jQuery to accomplish what you are trying to do:
/* your jquery file */
jQuery.ajaxSetup({
'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
})
...
$('#button').click(function() {
$.post('/controller/action', {
query_string1: value1
});
});

Resources