How do you add a custom HTTP response header in Rails? - ruby-on-rails

I'm looking to add custom HTTP response headers to a Ruby on Rails app that is currently hosted on Heroku.

Use:
response.headers['HEADER NAME'] = 'HEADER VALUE'
either in a specific method or to a before_filter method of your application controller depending on whether you need this to be added in a specific or to all of your responses.
UPDATE for Rails 5 - February 24th, 2018
As noted by #BrentMatzelle in the comments, for Rails 5:
response.set_header('HEADER NAME', 'HEADER VALUE')

In rails 5, the following solution works (in action methods)
response.set_header("Header-Name", "Header value")
Reference: edgeapi

In Rails 3 or above, simply
headers['Header-Name'] = 'header value'
works in controllers. This is even the recommended way; according to the documentation,
Response is mostly a Ruby on Rails framework implementation detail, and should never be used directly in controllers. Controllers should use the methods defined in ActionController::Base instead. For example, if you want to set the HTTP response’s content MIME type, then use ActionController::Base#headers instead of Response#headers.
And this is still true in Rails 7.0.

In rails 4, set the response headers in the application.rb or respective environment files. Once you done that, you can override the header value wherever you required in the controller. Refer this url for more details.

In rails 4 works following:
class API::V1::BaseController
after_action :set_version_header
protected
def set_version_header
response.headers['X-ComanyName-Api-Version'] = 'V1'
end
end

If your headers are static, e.g. your own custom Server header, you can simply update config.action_dispatch.default_headers. The following example sets a custom Server header; add it to your config/application.rb or config/environments/...:
config.action_dispatch.default_headers["Server"] = "MyServer/#{config.version}"
(Assuming you set config.version earlier)
For more, see Rails Guides: Configuring Rails Applications: Configuring Action Dispatch:
config.action_dispatch.default_headers is a hash with HTTP headers that are set by default in each response.
This will be less work each request than running a controller callback.
NB: For more than one header use merge! to not remove existing essential XSS etc headers.

Related

Perform HTTP OPTIONS request from Rails 5 for CORS pre-flight or otherwise

Edit: I re-added the options method in a pull request to Rails which should now be live. The answer below should no longer be necessary. Call process(:options, path, **args) in order to preform the options request.
See commit 1f979184efc27e73f42c5d86c7f19437c6719612 for more information if required.
I've read around the other answers and none of them seemed to work in Rails 5. It's surprising that Rails doesn't just ship with an options method, but here we are. Of course if you can use xdomain, you probably should (edit: I no longer hold this view, there are advantages to CORS) because it's both faster (no preflight check doubling latency!), easier (no need for silly headers / HTTP methods!), and more supported (works basically everywhere!) but sometimes you just need to support CORS and something about the CORS gem makes it not work for you.
At the top of your config/routes.rb file place the following:
match "/example/",
controller: "example_controller",
action: "options_request",
via: [:options]
And in your controller write:
def options_request
# Your code goes here.
end
If you are interested in writing an integration test there is some misinformation around the process method, which is not actually a public method. In order to support OPTIONS requests from your integration tests create an initializer (mine is at: config/initializers/integration_test_overrides.rb because I override a number of things) and add the following code:
class ActionDispatch::Integration::Session
def http_options_request(path)
process(:options, path)
end
end
So that you can call http_options_request from your integration test.

InvalidCrossOriginRequest when trying to send a Javascript Asset

I'm trying to create an "asset controller" shim which will filter static asset requests so only authorized users can get retrieve certain assets. I wanted to continue to use the asset pipeline so I setup a route like this
get 'assets/*assetfile' => 'assets#sendfile'
Then I created an AssetsController with one method "sendfile". Stripping it down to only the stuff that matters, it looks like this:
class AssetsController < ApplicationController
def sendfile
# Basically the following function forces the file
# path to be Rails.root/public/assets/basename
assetfilename=sanitize_filename(params[:assetfile] + '.' + params[:format])
send_file(assetfilename)
end
end
It looks like I have to run this in production mode as rails by-passes my route for assets in development. So I precompile my assets and I can verify in the controller that the files exist where they are expected to be.
However, now the problem is that I'm getting a "ActionController::InvalidCrossOriginRequest" when the Javascript asset is requested (just using the default application.* assets for now). I've read about this error and I understand that as of Rails 4.1 there are special cross-origin protections for Javascript assets. Sounds good to me, but I don't understand where the "cross-origin" part is coming from. Using firebug, I can see that the asset requests are being requested from the same domain as the original page.
I am certain that this is the problem because I can solve it by putting "skip_before_action :verify_authenticity_token" in the beginning of my controller. However, I really don't want to do this (I don't fully understand why this check is necessary, but I'm sure there are very good reasons).
The application.html.erb file is unchanged from the default generated file so I assume it's sending the CSRF token when the request is made, just as it would if I didn't have my own controller for assets.
So what am I missing?
Ok, I think I answered my own question (unsatisfactorily). Again, long post, so bear with me. I mistakenly forgot to add this to my original questions, but I'm using Ruby 2.2.0 and Rails 4.2.4.
From looking at the code in "actionpack-4.2.4/lib/action_controller/metal/request_forgery_protection.rb", it looks like Rails is doing two checks. The first check is the "verify_authenticity_token" method which does the expected validation of the authenticity token for POST requests. For GET requests, it ALSO sets a flag which causes a second check on the formed computed response to the request.
The check on the response simply says that if the request was NOT an XHR (AJAX) request AND the MIME Type of the response is "text/javascript", then raise an "ActionController::InvalidCrossOriginRequest", which was the error I was getting.
I verified this by setting the type to "application/javascript" for ".js" files in "send_file". Here's the code:
if request.format.js?
send_file(assetfilename, type: 'application/javascript')
else
send_file(assetfilename)
end
I can skip the response check all together by just adding the following line to the top of my controller class:
skip_after_action :verify_same_origin_request
The check on the response seems pretty weak to me and it's not clear how this really provides further protection against CSRF. But I'll post that in another question.

Rails - How to obtain an absolute URL for the site? https://example.com

For one of my models I have a method:
def download_url
url = xxxxx
end
which works nicely to make /xxxx/xxxx/3
What i want to do is updated this to include an absolute URL so I can use this method in an email:
https://example.com/xxxx/xxxx/3
But I don't want to hard code. I want it to be an environment var so it works on dev & production
Emails are effectively views, and can use helpers. The model shouldn't really have any knowledge about the views - instead, you should use url_for or one of its descendant methods in the email view template to generate a URL. Those helpers can generate absolute URLs based on the location that the application is running (and associated configuration - you'll want to set config.action_mailer.default_url_options[:host] in your environment file) without having to mess with environment variables and the like.
I would define the domain as a constant in development.rb & production.rb:
APP_DOMAIN = "https://mysite.com"
And then just use this constant in your method within the model:
def download_url
"#{APP_DOMAIN}/download/#{id}"
end
It may be ugly, but it's necessary. Rails apps don't and shouldn't know their root URL. That's a job for the web server. But, hardcoding sucks...
If you're using capistrano or some other deployment method, you can define the server host in a variable and write it out to a file that you can read from the app.

Rails 3 is "_method=PUT" still supposed to work?

I'm using FLEX 3 to make XML request to Rails 3. Since FLEX 3 only offers POST and GET, I have to use the "?_method=PUT" hack to maintain proper RESTfulness:
http://127.0.0.1:3000/locator/locator_users/1.xml?_method=PUT
On the server side its showing up as a POST and I'm getting an ActionController::RoutingError (No Route Matches).
I did a rake routes and the route is there, properly namespaced and all.
This worked fine with Rails 2, so I have reason to believe it must be Rails 3 that changed. After doing some searching, people seemed to have indicated that it should still work. But, it's not for me. Can anyone confirm or deny Rails 3 compatibility?
UPDATE
OK, after some more tinkering, I think this is actually a problem of Flash Player 10. Flash PLayer 9 seems to work fine with "_method=" hack, 10 does not. See this new post I wrote (Flash Player 9 vs Flash Player 10 with FLEX 3, ?_method=PUT/DELETE not working?).
Partly this is due to Rack::MethodOverride's behavior. It does not check the query params for _method, so a call to http://127.0.0.1:3000/locator/locator_users/1.xml?_method=PUT
will not get overridden properly due to that.
I wrote a piece of Rack middleware that replaces it to fix this particular issue.
All you have to do is
add it to the Gemfile
gem 'rack-methodoverride-with-params'
swap Rack::MethodOverride out in config/environment.rb
config.middleware.swap Rack::MethodOverride, Rack::MethodOverrideWithParams
If you can add _method=PUT in your request body, then no need to swap out the rack middleware.
If you cannot do that, then I've come across another (lower-impact) solution, which is to simply define a custom route that accomplishes what you're looking for, for example:
# config/routes.rb
post '/locator/locator_users/:id', to: 'locator_users#update', constraints: {_method: 'POST'} # allow http method override
Granted, you would have to add this route for every resource you need HTTP method override on, but that might be a good thing if you want to limit your exposure to potential weirdness since this breaks HTTP semantics.
EDIT: You can do the same thing with GET requests if you need to, just swap out post for get (this can be useful if you need to support REST over JSONP).
You might have to rewrite how you're making your request to the server. I'm using Rails 3, Flex 4, and Flash 10 together (but with an app developed in Flex 3) and use _method as a parameter in my HTTPService object (leaving the content-type as the default application/x-www-form-url).
HTTPService only supports GET and POST requests. If you use set useProxy property to true on the HTTPService object, you can use HEAD, OPTIONS, TRACE, and DELETE but only if you are also using a server-based proxy service. If this doesn't work, then you may want to try URLLoader or URLRequest or implementing your own custom solution instead.

Filtering parts or all of request URL from rails logs

Rails provides filter_parameter_logging to filter sensitive parameters from the rails log.
If you have a a JSONP API, some sensitive information could be present in the URL. Is there a way to filter request URLS from the log also?
Note: The answer here was the way to get it work on Rails 2.x ~> 3.0. Starting from Rails 3.1, if you set config.filter_parameters, Rails will filter out the sensitive parameter in the query string as well. See this commit for more detail.
I think in that case, you need to override complete_request_uri in ActionController::Base, since ActionController::Benchmarking calls that method and prints the line that looks like:
Completed in 171ms (View: 35, DB: 7) | 200 OK [http://localhost:3000/]
I think you can put this in initializer to override this method
class ActionController::Base
private
def complete_request_uri
"#{request.protocol}#{request.host}#{request.request_uri.gsub(/secret=([a-z0-9]+)/i, "secret=[FILTERTED]")}"
end
end
Note that you need to play a bit with regular expression to make it substitute the portion you wanted.
Sadly this no longer works in Rails 3. The path (including query parameters) comes from ActionDispatch::Request, which inherits from Rack::Request. Here's the relevant monkeypatch that you can throw into an initializer:
class ActionDispatch::Request
def fullpath
#fullpath ||= super.gsub(/secret=[^&]+/, 'secret=[FILTERED]')
end
end
I switched to using [^&] in the regex since the parameter could easily have characters that aren't letters or numbers in it.

Resources