Cross-Origin Request Blocked with Sinatra and ReactJS - ruby

I am building a simple Sinatra backend with ReactJS frontend. When I try to make request to a route in my Sinatra project from React app it gives me CORS error. I tried to enable CORS in my project like this but it didn't work:
require 'sinatra'
require 'sinatra/cross_origin'
require 'json'
configure do
enable :cross_origin
end
set :allow_origin, :any
set :allow_methods, [:get, :post, :options]
set :allow_credentials, true
set :max_age, "1728000"
set :expose_headers, ['Content-Type']
get '/' do
'Hello!'
end
post '/download' do
content_type :json
return {res: params['songs']}.to_json
end
So when I do a request like this from React:
axios.post('http://localhost:4567/download', {}, {
songs: this.state.songs
}).then(res => {
console.log(res.data)
})
I get a CORS error which looks like this:
And I get this error in the console:
What should I change in my Sinatra/React project to make this work so I can make requests from React to Sinatra?

See https://github.com/britg/sinatra-cross_origin#responding-to-options. You need to add your own code to manually handle OPTIONS requests — because the sinatra-cross_origin gem itself doesn’t actually handle OPTIONS requests. Specifically, you’d need to add this:
options "*" do
response.headers["Access-Control-Allow-Methods"] = "HEAD,GET,PUT,POST,DELETE,OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
200
end

I had the exact issue as you with Sinatra & React, after countless hours of searching I couldn't find any solution until I found this gem, https://github.com/jdesrosiers/sinatra-cors
Here's my solution which implements the basics:
require "sinatra/cors"
set :allow_origin, "*"
set :allow_methods, "GET,DELETE,PATCH,OPTIONS"
set :allow_headers, "X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept, if-modified-since"
set :expose_headers, "location,link"

Related

Rails 5 api controller not receiving params sent by frontend app [duplicate]

I seem to be running into some issues making a GET request to an API endpoint. I know rails has some security going on behind the scenes. I'm using React-Rails and in my componentDidMount to make an ajax call to an API endpoint. I am passing in a X-Auth-Token in my headers too.
My console error:
XMLHttpRequest cannot load "/api/end/point..." Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. The response had HTTP status code 401.
My ajax call is looking like
$.ajax({ url: "my/api/endpoint...", headers: {"X-Auth-Token": "My API token..."},
success: (response) => { console.log(response); } })
Because your frontend will do requests from any origin (any client), you have to enable CORS from any origin. Try
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
I use this in a rails 5 api application. Before rails 5 add rack-cors gem to your Gemfile. Anyway, restart your server.
rails 3/4
# config/application.rb
module YourApp
class Application < Rails::Application
config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
end
end

Faraday Connection: Switching the request mode?

I am using faraday to handle some requests to an internal API. The API endpoints use a CSRF token, so I am also using faraday-cookie_jar.
For a couple of the API endpoints, they require a :multipart request. Others do not.
Question
Is there any way to utilize the same Connection object, but switch whether or not you are doing a :multipart or a :url_encoded request?
Currently, I have to use two connections depending on which type of request I'm making. It does not seem you can change a connection's request method after it has done at least 1 request.
#connection = Faraday.new(url: 'http://localhost:3000') do |faraday|
faraday.request :url_encoded
faraday.use :cookie_jar
faraday.adapter Faraday.default_adapter
end
#connection.get '/something'
# try to change to :multipart
#connection.request :multipart # => Faraday::RackBuilder::StackLocked: can't modify middleware stack after making a request
It doesn't seem to allow you to switch after you've made a request. I know that you can modify the request a bit for each request itself by passing a block, but I can't seem to find where to modify to switch to :multipart in it.
Thanks in advance.
You'll want to include both middleware options when creating your connection (:url_encoded and :multipart), and then switch using explicit Content-Type headers.
Per the Faraday ReadMe, you can specify 'em both. An excerpt:
Faraday.new(...) do |conn|
# POST/PUT params encoders:
conn.request :multipart
conn.request :url_encoded
conn.adapter :net_http
end
This request middleware setup affects POST/PUT requests in the following way:
Request::Multipart checks for files in the payload, otherwise leaves everything untouched;
Request::UrlEncoded encodes as "application/x-www-form-urlencoded" if not already encoded or of another type.
So adding :multipart still allows for url-encoded posts, because it only matters when there are files in the payload. Then, if you explicitly set your Content-Type for the file upload, you should be fine - Faraday will use the correct request middleware because you explicitly told it to use multipart. But if you don't specify, it will default to url-encoded.
# works using :url_encoded
#connection.post '/something' do |req|
req.body = { some: 'posted fields' }
end
# works using :multipart because Content-Type was explicitly set
#connection.post '/some_file_endpoint' do |req|
req.headers['Content-Type'] = 'multipart/form-data'
req.body = { file_field: Faraday::UploadIO.new(file_path, 'text/plain') }
end
Calling #connection.request :multipart adds Faraday::Request::Multipart to #connecton.builder.handlers. If you want to remove something, you can manipulate that array.
I make no claim that messing with Faraday's (relative) internals is a good idea. Keeping your two connections sounds like a better plan.
Sean’s answer sounds like the right thing to do here, but if you do want to modify your connection, you have to duplicate it before:
#connection = #connection.dup
#connection.request :multipart
This can also be used to delete middleware from a connection:
#connection = #connection.dup
#connection.builder.delete(Faraday::Request::lookup_middleware(:multipart))

Can't generate an OAuth1 Authorization header using Typhoeus in Ruby

I'm trying to send a request via Typhoeus to Bitbucket's API using Ruby, and I'm doing so with credentials from the "OAuth consumers" page - the consumer_key and consumer_secret. I can get the token just fine, but I get an error when trying to generate the header from the OAuth helper. Here's the relevant code (note that this is not Rails; I'm using Sinatra):
# oauth_token and oauth_token_secret are from a request to https://bitbucket.org/api/1.0/oauth/request_token
#consumer = OAuth::Consumer.new(
consumer_key,
consumer_secret,
)
#token = OAuth::Token.new(oauth_token, oauth_token_secret)
options = {
method: :post,
body: body.to_json
}
oauth_params = {:consumer => #consumer, :token => #token}
hydra = Typhoeus::Hydra.new
url = "https://api.bitbucket.org/2.0/repositories/..."
req = Typhoeus::Request.new(url, options)
oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(:request_uri => url))
req.options[:headers].merge!({"Authorization" => oauth_helper.header})
The last line fails with a OAuth::RequestProxy::UnknownRequestType error. Basically what it's saying is that the OAuth RequestProxy doesn't understand the Typhoeus::Request object; from what I can see, it only has support for Net::HTTPGenericRequest and Hash. The Typhoeus documentation indicates that this should work, so it's possible I'm doing something wrong.
Alternatively, is there a better way to do this? Should I use a different request library? HTTParty doesn't have great support for auth headers like this. Thanks!
You need to explicitly require the RequestProxy.
require 'typhoeus'
require 'oauth'
require 'oauth/request_proxy/typhoeus_request'

POST JSON response to HTTP request in Ruby

I'm running a Ruby app on Heroku. The app returns a JSON which is accessible when I go to the debugger of my browser. The JSON response is of the following template:
rates = {
"Aluminium" => price[1],
"Copper" => price_cu[1],
"Lead" => price_pb[1],
"Nickel" => price_ni[1],
"Tin" => price_sn[1],
"Zinc" => price_zn[1],
}
Sample response:
{
"Aluminium":"1765.50",
"Copper":"7379.00",
"Lead":"2175.00",
"Nickel":"14590.00",
"Tin":"22375.00",
"Zinc":"2067.00"
}
the code i wrote to achieve this is:
Test.rb
class FooRunner
def self.run!
#calculations_for_rates
rates.to_json
end
if __FILE__ == $0
puts FooRunner.run!
end
app.rb
require 'sinatra'
require './test.rb'
result = FooRunner.run!
File.open('output.json','w') do |f|
f.write result
end
content_type :json
result
When I try to access this link using
$.getJSON('app-url',function(data){
console.log(data);
});
it gives me an error saying
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Is there a way for me to directly access the JSON response by writing the JSON to the HTTP response?
So I am guessing that the page you are making the get request from is not served up by Sinatra. You can add the header Access-Control-Allow-Origin: * to that request to make it work.
This answer shows how to do it by either using response['Access-Control-Allow-Origin'] = * or headers( "Access-Control-Allow-Origin" => "*" )
That answer also lists this blog post as a reference to Cross Origin Resource Sharing in Sinatra.

How to add response header in VHost or Passeneger (Ruby)

I have problem with Same Origin Policy. I want to make cross domain request - I found nice solution: http://www.w3.org/TR/cors/
But I don't want set header in Apache because I have there many domains and only one need it. Is it possible to add Access-Control-Allow-Origin header via Virtual Host or Passenger?
I do it, because I need use Redmine REST API (XHR) in Chrome/Mozilla plugin.
I had a similar requirement. If you want Redmine to serve these headers then you need to modify the Redmine source. I've written a blog post about doing this.
Credit to this blog post for most of the details.
I'll reproduce what I had to do here for convenience:
First let's adress the preflight check. I've added a whole new controller, just for this, at /app/controllers/cors_controller.rb. It looks like:
class CorsController < ApplicationController
skip_before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
def preflight
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS, PUT'
headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-Prototype-Version, Content-Type'
headers['Access-Control-Max-Age'] = '1728000'
render :text => '', :content_type => 'text/plain'
end
end
Pretty simple stuff. I've then routed all OPTIONS requests to this controller in /config/routes.rb:
match '*path', :to => 'cors#preflight', :constraints => {:method => 'OPTIONS'}
Preflight checks taken care of, it's just a case of adding the headers to the main response using an after_filter in /app/controllers/application_controller.rb as suggested by Tom:
class ApplicationController < ActionController::Base
include Redmine::I18n
# ...
before_filter :session_expiration, :user_setup, :check_if_login_required, :set_localization
#************ Begin Added Code ****************
after_filter :cors_set_access_control_headers
# For all responses in this application, return the CORS access control headers.
def cors_set_access_control_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS, PUT'
headers['Access-Control-Max-Age'] = "1728000"
end
#************* End Added Code *****************
#...
end

Resources