please help a newbie in Rails :) I have protect_from_forgery call (which is given by default) with no attributes in my ApplicationController class.
Basically here's the code:
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery
helper_method :current_user_session, :current_user
filter_parameter_logging :password, :password_confirmation
What I assume it should do is: it should prevent any POST requests without correct authenticity_token. But when I send post request with jQuery like the one below, it works fine (there's update statement that is executed in the database)!
$.post($(this).attr("href"), { _method: "PUT", data: { test: true } });
I see in console that there's no authenticity_token among sent parameters, but request is still considered valid. Why is that?
UPD
Found config setting in config/environments/development.rb
config.action_controller.consider_all_requests_local = true
Because of the DEV environment and local requests, these jQuery post requests were OK.
There is nothing wrong with your code as long as the request $.post($(this).attr("href"), { _method: "PUT", data: { test: true } }); is executed from within the app itself. If you had another app running elsewhere, say for example on localhost:3001, and you sent a post from there then it won't work. Infact if you are on firefox > 3.0 it has an early implementation of cross site xhr too. For example you can send a POST from any other site (but this works provided protect_from_forgery is turned off!). The reason why auth token is not necessary for xhr is that cross site xhr is disabled. So it is safe to use xhr without providing auth token. If you try from any where else other than your app, i am sure it will raise an exception asking for an auth token. Also you should have a crossdomain.xml defined to prevent access from outside sources.
Try doing this: curl -X -d url_endpoint_of_your_app. See if you get a 200 response code. If you do then there is something fishy.
Silly question, perhaps: Are you sure you're subclassing ApplicationController? What's your route map look like? And what version of Rails (just for clarity)?
Did you verify that the call from jQuery is actually a POST and not a GET? (I know, it seems obvious). Rails will only perform the protection on non-GET requests.
Also, what is the content-type of the request that's going out. Rails will also only perform the protection, according to the docs, if it's an HTML/Javascript request.
Related
I'm working on a platform built with Ruby(v2.1.2) on Rails(v4.1.6) and we're trying to enforce SSL. How do I go about doing this?
So far, I have force_ssl
# application_controller.rb
force_ssl if: :ssl_configured?
def ssl_configured?
return false if params[:controller] == 'high_voltage/pages'
(Rails.env.development? || Rails.env.test?) ? false : true
end
Which seems to work because when I try to do http://www.somecoolsite.com, it then automatically becomes https://www.somecolesite.com.
However, if I try to submit a JSON post request to the API portion of our platform, and the URL is http://, the post request is somehow returning the results of a get request instead. But when I change the URL to https://, the post request works as expected. How would I go about fixing this so that if a client accidentally submits their request as http://, it is rewritten to https://?
Thanks!
Have you looked at using config.force_ssl = true in your production environment config? This is far more all-encompassing, but can lead to other issues as well.
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.
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.
I am trying to test a responsive design. I am using Rails 4.
I know it sets 'X-Frame-Options' to SAME ORIGIN. So I overrided it in development.rb using
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'ALLOWALL'
}
and it worked. I checked out the network request in the Chrome console and it is as follows:
But still websites like responsive.is and responsinator.com give me below error:
Refused to display 'http://localhost:3000/' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'. about:blank:1
Whats going on??
Try just to delete this header 'X-Frame-Options'.
Maybe this way in controller:
before_filter :allow_iframe_requests
...
def allow_iframe_requests
response.headers.delete('X-Frame-Options')
end
I had the same problem as you, and searched for a solution to this problem all night.
I finally found out why it happens. It's because of the Chrome cache.
You can see the header['X-Frame-Options'] is ALLOWALL but it doesn't work.
Just try to open a "New Incognito Window" and go the same page and it works!
This problem only happened in development mode in my test. It worked fine in production mode.
Rails 4 added a default X-Frame-Options HTTP header value of SAMEORIGIN. This is good for security, but when you do want your action to be called in an iframe, you can do this:
To Allow all Origins:
class MyController < ApplicationController
def iframe_action
response.headers.delete "X-Frame-Options"
render_something
end
end
To Allow a Specific Origin:
class MyController < ApplicationController
def iframe_action
response.headers["X-FRAME-OPTIONS"] = "ALLOW-FROM http://some-origin.com"
render_something
end
end
Use :after_filter
When you need to use more than one of your action in an iframe, it's a good idea to make a method and call it with :after_filter:
class ApplicationController < ActionController::Base
private
def allow_iframe
response.headers.delete "X-Frame-Options"
end
end
Use it in your controllers like this:
class MyController < ApplicationController
after_filter :allow_iframe, only: [:basic_embed, :awesome_embed]
def basic_embed
render_something
end
def awesome_embed
render_something
end
# Other Actions...
end
Do a Hard-Refresh in your browser, or use another browser to view changes
Via: Rails 4: let specific actions be embedded as iframes
When 'Load denied by X-Frame-Options' using Heroku & Firefox
I had a similar issue where I kept getting this error only on Firefox. I had a PHP web page hosted # MochaHost serving a Rails app hosted # Heroku (so RoR app has a page with an iframe which is pointing to the PHP web page and this working on all browsers except on Firefox).
I was able to solve the problem by setting a default header for all of my requests in the specific environment file:
# config/enviroments/production.rb
config.action_dispatch.default_headers = { 'X-Frame-Options' => 'ALLOWALL' }
Edit
(as sheharyar suggested)
Ideally, you shouldn't set a default header and do this only for actions that have to be rendered in an iFrame. If your entire app is being served inside an iFrame, you should explicitly mention the Origin:
# config/enviroments/production.rb
config.action_dispatch.default_headers = { 'X-Frame-Options' => 'ALLOW-FROM http://some-origin.com' }
Try ALLOW-FROM http://example.com instead? ALLOWALL might be ok in Chrome if you have a sufficiently new version of Chrome [2]
[1] https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
[2] https://stackoverflow.com/a/16101968/800526
I found another cause for this. Assuming the ALLOWALL or similar fix is implemented, the next gotcha is attempting to use http content in a https website which causes security risks and is blocked by mozilla, IE and probably other browsers. It took me 6 hours to identify this, hopefully by sharing I can reduce someones pain...
It can be checked by:
using your browser web-tools which should display an error.
web logs will lack any connection with your supplying site.
replace your contents url with a banks https home page should demonstrate the iframe otherwise works.
The solution is to ask the source if they have https content or find another supplier.
ref:
https://developer.mozilla.org/en/docs/Security/MixedContent
https://developer.mozilla.org/en-US/docs/Security/MixedContent/How_to_fix_website_with_mixed_content
I just wanted to give an updated answer here on dealing with embedding a Rails app in an iframe.
Its not a great idea to simply delete X-Frame-Options headers without having some other kind of security enforced to prevent against Clickjacking (which is the vulnerability X-Frame-Options is largely trying to protect you from).
The problem is that the X-Frame-Options 'ALLOW-FROM' option is not accepted on most major browsers anymore.
As of writing this, May 28th 2020, the best solution for preventing Clickjacking and hosting your app in an iframe is to implement a Content-Security-Policy and set a 'frame_ancestors' policy. The 'frame_ancestors' key designates what domains can embed your app as an iframe. Its currently supported by major browsers and overrides your X-Frame-Options.
You can set up a Content-Security-Policy with Rails 5.2 in an initializer (example below), and for Rails < 5.2 you can use a gem like the Secure Headers gem: https://github.com/github/secure_headers
You can also override the policy specifications on a controller/action basis if you'd like.
Content-Security-Policies are great for advanced security protections. Check out all the things you can configure in the Rails docs: https://edgeguides.rubyonrails.org/security.html
A Rails 5.2 example for a Content-Security-Policy:
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.frame_ancestors :self, 'some_website_that_embeds_your_app.com'
end
An example of a controller specific change to a policy:
# Override policy inline
class PostsController < ApplicationController
content_security_policy do |p|
p.frame_ancestors :self, 'some_other_website_that_can_embed_posts.com'
end
end
If you want to have this change take effect in all environments, place it in application.rb.
When i tried to post data through my REST Client ,i am getting a warning like this
Warning :Can't verify CSRF token authenticity.
How to solve this.
I believe you are trying to make a POST from a link. By default links aren't supposed to make POST requests, only GET ones. So when you try to make the connection to your server, Rails warns you about this.
One way to bypass this kind of behavior is, instead of using a link, use a form. So you can make proper POST requests to the server.
Alternatively, you can remove the protect_from_forgery line from your application_controller, so Rails doesn't do this kind of verification, but this is extremely not recommended.
You could do this, (but not recommended :)), skip forgery protection
Ex: you are posting data to PostController => Create action
class PostsController < ApplicationController
before_filter :protect_from_forgery, :except => [:create]
def create
#your method
end
end
but having said that, I'm sure there should be a better way to do what you want to do, so if you could explain what you want to do, someone could help
HTH
nop
I missed the following line in my application.js
//= require jquery_ujs
I replaced it and its working..