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

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

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))

Devise Json Authentication

I am writing backend of an app in Rails. As I work on the backend, I need to give the frontend developer a REST API to start building the frontend. Eventually, the frontend and backend will reside together in a single app, but for now they are separate.
For time being I have enabled Cross-origin resource sharing in my app, by adding following to ApplicationController:
config.action_dispatch.default_headers.merge!({
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => '*'
});
For now, I have also turned off CSRF tokens by adding following to application.rb:
skip_before_filter :verify_authenticity_token
I am using Devise for authenticating users. To make Devise work with JSON requests, I have done following:
In devise.rb
config.navigational_formats = ['*/*', :html, :json]
In routes.rb
devise_for :users, :controllers => {:omniauth_callbacks => "omniauth_callbacks", :sessions => 'sessions', :registrations => 'registrations' }
My SessionsController
class SessionsController < Devise::SessionsController
#todo had to do following to support logging in through ajax. need to add logic to send back error response when login fails.
#todo see http://stackoverflow.com/questions/5973327/using-devise-1-3-to-authenticate-json-login-requests/8402035#8402035 and
#todo https://web.archive.org/web/20130928040249/http://jessehowarth.com/devise
#todo see http://stackoverflow.com/questions/11277300/devise-failure-authentication-via-json-sends-back-html-instead-of-json
def create
respond_to do |format|
format.html { super }
format.json {
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
sign_in(resource_name, resource)
return render :json => {:success => true, :user => resource}
}
end
end
def destroy
respond_to do |format|
format.html { super }
format.json {
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
render :json => {}
}
end
end
def failure
render :json => {:success => false, :errors => ["Login Failed"]}, :status => 422
end
end
I have a extended Devise's RegistrationsController as well as indicated in routes.rb, but am not posting its content here, as I don't think it is relevant to this question.
With the above setup I am able to send an ajax request to '/users/sign_in' with user[email] and user[password] parameters and have the user signed in. The response looks something like this:
{
success: true
user: {
authentication_token: "SNa2kPqkm5ENsZMx7yEi"
created_at: "2014-12-16T02:40:39.179Z"
email: "xyz#xyz.com"
id: 99999
name: null
provider: null
uid: null
updated_at: "2014-12-17T02:29:31.537Z"
}
}
Now how do I use the authentication_token I received in the sign_in response to send requests to other controller actions that require user to be authenticated? Do I need to set this token in a request header? I am not able to find information on how to use this token. Please help.
It seems following as described in the gist here, the answer is that you send the suer's email and authetication_token with every request to the backend. You may choose to send it in request header or simply as parameters. You simply modify the method that checks the email and token and signs in the user in ApplicationController accordingly. This is my ApplicationController (I am now sending the email and token as parameters in the request):
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
#todo remove this once ui is integrated. following turns off the csrf token:
skip_before_filter :verify_authenticity_token
#todo begin code to support authentication using token
# This is our new function that comes before Devise's one
before_filter :authenticate_user_from_token!
# This is Devise's authentication
before_filter :authenticate_user!
private
def authenticate_user_from_token!
user_email = params[:user_email].presence
user = user_email && User.find_by_email(user_email)
# Notice how we use Devise.secure_compare to compare the token
# in the database with the token given in the params, mitigating
# timing attacks.
if user && Devise.secure_compare(user.authentication_token, params[:user_token])
sign_in user, store: false
end
end
#todo end code to support authentication using token
end
I forgot to mention in my post that I had already added the migration to add a authentication_token column to User model. Also, I had to add following in the User model (as described in the gist), so that an authentication token is generated each time a user is created/updated:
#todo begin code to support ajax authentication of users
#todo see https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
# You likely have this before callback set up for the token.
before_save :ensure_authentication_token
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.where(authentication_token: token).first
end
end
#todo end code to support ajax authentication of users

Using CORS with Rails 4 and heroku

I'm using Rails 4 on heroku and need to enable CORS.
I've looked around the web for hours now and tried various solutions, which worked for many other people, but not for me.
My last try was to simply add CORS to all requests in the application controller:
before_filter :cors_preflight_check
after_filter :set_headers
def set_headers
#if request.headers["HTTP_ORIGIN"]
# better way check origin
#if request.headers["HTTP_ORIGIN"] && /^https?:\/\/(.*)\.(.*)\.cloudfront\.net$/i.match(request.headers["HTTP_ORIGIN"])
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
headers['Access-Control-Allow-Headers'] = %w{Origin Accept Content-Type X-Requested-With auth_token X-CSRF-Token}.join(',')
headers['Access-Control-Max-Age'] = "1728000"
#end
#end
end
def cors_preflight_check
if request.method == "OPTIONS"
headers['Access-Control-Allow-Origin'] = 'http://localhost'
headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
headers['Access-Control-Allow-Headers'] = %w{Origin Accept Content-Type X-Requested-With auth_token X-CSRF-Token}.join(',')
headers['Access-Control-Max-Age'] = '1728000'
render :text => '', :content_type => 'text/plain'
end
end
The commented lines were a try to make all of it more clean. However, nothing of this works. This is how this thread told me to do.
Do you have any idea or hint how I can get this to work?
Thanks in advance!
So you have just about everything I have but I don't think you're matching the options request to your cors_preflight_check method. You can also remove the before filter on cors_preflight request since options will be routed there by default. And if you have any authentication for requests it needs to be removed for options requests. Check this out https://github.com/cleor41/Cors-Rails4-API and I'll post the important parts here.
This goes in the application controller for your API
#APIController
before_action :authenticate_user
after_filter :cors_set_access_control_headers
skip_before_filter :authenticate_user, :only => [:route_options]
def route_options
cors_preflight_check
end
private
def authenticate_user
#Do some cool stuff with tokens to identify the user
end
def cors_set_access_control_headers
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, PATCH, DELETE, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization, Token, Auth-Token, Email'
response.headers['Access-Control-Max-Age'] = "1728000"
end
def cors_preflight_check
if request.method == 'OPTIONS'
request.headers['Access-Control-Allow-Origin'] = '*'
request.headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, PATCH, DELETE, OPTIONS'
request.headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-Prototype-Version, Token, Auth-Token, Email'
request.headers['Access-Control-Max-Age'] = '1728000'
render :text => '', :content_type => 'text/plain'
end
end
This goes in your routes and matches to the cors_preflight_check in your api application controller
#Routes
#Route this to wherever you put the cors_preflight_check
#This is to handle the CORS preflight request, it only catches the options action.
controller 'api/v1/api' do
match '*unmatched_route', :to => 'api/v1/api#route_options', via: [:options]
end
Hope this works for you, let me know if it doesnt.

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.

Resources