I'm sending an AJAX request from my rails site to itself (to go from javascript to a controller). Rails refuses to allow the POST unless I supply an authenticity token, so I added one using
<%= csrf_meta_tags %>
and
var AUTH_TOKEN = "<%=j form_authenticity_token %>"
and everything was fine. However, a new customer recently installed the plugin that accesses my site and triggers the AJAX in the first place. But for this one customer--the authenticity token was denied, despite being supplied (I checked in the logs.)
I realize I'm not giving a lot of clues to go off of, but what could cause the authenticity token to be accepted in one situation and denied in another? More broadly, how is the authenticity_token generated anyways--a new one every single time the page is loaded?
Rails assigns a cryptographically random CSRF token to the the user session.
The server compares the value submitted for the authenticity_token parameter to the value associated with the user’s session.
One thing you especially need to be careful with is that if you are using fragment caching (which speeds up rendering by caching chunks of the view) you need to ensure that your <%= csrf_meta_tags %> are not cached since a stale csrf meta tag will lead to mismatch with the token stored in the session.
When posting with ajax, you need to forward the CSRF token with the X-CSRF-Token header.
var promise = $.ajax({
url: '/example',
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token',
$('meta[name="csrf-token"]').attr('content'))
},
data: 'someData=' + someData
});
Related
My task is to write a feature in a javascript package that will be embedded in html sites for form validation. Validation will be done through API call to my server.
The question is how to transfer and validate CSRF token from JS to my Rails server.
I've tried doing this:
var token = function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))};
$.ajax({
url: 'http://localhost:3000/csrf-check',
type: 'POST',
beforeSend: token,
data: {
hey: 'hey'
},
success: function(response) {
console.log(response);
}
})
In my ValidatorController:
class ValidatorController < ApplicationController
protect_from_forgery
def csrf_check
if session[:_csrf_token]
render json: :ok
else
render json: :fail
end
end
end
How can I send CSRF token and validate it on Rails server?
First of all: I don't really understand why you'd want to do such a thing – see max' comment.
Either way, if you want to do some kind of manual check for the authenticity token, you need to disable Rails' own facilities for this particular endpoint and then use the logic behind it in your endpoint. While I haven't tested it, I'd assume something like this should work:
class CsrfTokenChecksController < ApplicationController
skip_forgery_protection
def show
head any_authenticity_token_valid? ? :ok : :bad_request
end
end
This uses the any_authenticity_token_valid? method (see https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html#method-i-any_authenticity_token_valid-3F) which is used internally by Rails as part of its forgery protection filter.
Note: This method exists since Rails 5. If you're using Rails 4, you'd have to go with valid_authenticity_token?(session, request.headers['X-CSRF-Token']) instead. For Rails 3, it would be form_authenticity_token == request.headers['X-CSRF-Token'].
I would consider if CSRF protection is really even applicable in your case.
Rails CSRF protection serves to guard against cross site reference forgery where another page pretends to be your page by looking visually similar and fooling the user into performing a POST request to for example steal the cookies for a replay attack.
It only actually works in the case where Rails is actually rendering the HTML as it just gives a guarantee that the the request originated from the same server that rendered the form.
If you're making something cross site on purpose it just won't work or even fill any purpose. There are other mechanisms like API keys that can be used instead to verify that the request is legit.
I have a rails app with a POST url which creates some resources.I have a page with a form which takes in all the information and does an AJAX call to the POST url without authenticity token.
Am doing data["authenticity_token"] = "";, before doing the AJAX call.
Parameters logged on serverside are like below
{"utf8"=>"✓", "authenticity_token"=>"", "company_customer"=>{"name"=>"Anand"}}
The resources are created without any error(I have protect_from_forgery with: :exception in my ApplicationController).
But when I tried to call the same POST url from Postman, I get InvalidAuthenticityToken error.
Why am I getting the error?
How does the rails app verify the authenticity of the POST request in first case?
In the first step i.e. from browser , you might be having some session id in the cookies but here not.
Also, if you were hitting by remote:true option, it will take the authenticity token from the page in the hidden field.
For more details , check your logs in both cases.
This token is automatically added in as hidden field when you are using form_for helper method to generate forms, so Rails makes sure the request comes from one of your forms.
You should unprotect your controller action when requested from postman or any other app, see how to here : https://stackoverflow.com/a/22715175/8352929
You can find how CSRF works from here. I recommend you go through it.
Whenever you use form_for Rails adds one hidden input field to your form which looks like the following:
<input type="hidden" name="authenticity_token" value="doLYVxrkhdrzn7zzriHXjFE6ZhNCuXVxLrau4ouENmuKKC/SWp2NMM/MeL/Ji2tDvzNcJHVN/Hc0LIluL3o5QQ==" />
Also, Rails include CSRF token in the meta tags of the website which looks like the following:
<meta name="csrf-token" content="zxnmBxg81JUQPG/C/wb3HRCah0m9Xe2A+gZ5N0Oy7cfwC+dF4hC325WxdVDLfkIxcw/CR/xyaC1phpvZ4EcgQw==" />
So, when you use Rails form_for or something similar to make AJAX call(may be with remote: true) the authenticity token is sent to the server. Which was not present when you tried to send the same request with Postman.
If you copy the CSRF token and add it to Postman params, the request will be completed successfully.
A JavaScript piece of code sends UserId when initializing WebSockets connection. This requires to pass UserId from Rails view to JavaScript source.
I do not like this solution for two reasons:
I want JS code to be entirely in *.js file without having to include it as partial into layouts
UserId is easy to counterfeit and listen messages addressed to other user
The better solution is obvious: in any case each WebSocket connection from an authenticated user is accompanied by Devise's SessionID in cookie. I already found how to extract this cookie from handshake data and now there is one problem:
The WebSockets listener is running constantly in a background thread whether some user is authenticated or not. So, it does not have an access to user's session.
The question:
How to get an user or UserId by SessionID if I am not authenticated. Does Devise have some SessionID storage in memory which I could access (probably with some nasty hack).
Devise stores stuff by warden within the user session. So if you have no access to the session, there is also none to the user_id within this separate thread.
Since your are exposing the user_id anyway in the script, you can just set it into an additional cookie.
You could also implement an api enpoint returning the id.
You can render it into the page within some invisible 'data-' attribute
<body data-user-id='asfasdfeefifpf'> ... </body>.
Or at least you could just keep the code in the js partial, which sets the id and extract the lib code into separate file.
<%= javascript_tag do %>
window.user_id = '<%= j current_user.id %>';
<% end %>
http://railscasts.com/episodes/324-passing-data-to-javascript?view=asciicast
I have a single page app that authenticates to another domain using CORS. All the requests are JSON requests.
My app can authenticates OK and can make GET requests OK. Authentication is using token_authenticatable. I.e. all requests append '?auth_token=whatever'
So, my actual problem is that when I try to do a PUT request I get a WARNING: Can't verify CSRF token authenticity message in the rails log as well as a CanCan::AccessDenied (You are not authorized to access this page.) exception.
Simply adding skip_before_filter :verify_authenticity_token to the rails controller fixes the issue.
Therefore I can only conclude that my ajax requests are sending an invalid or empty csrf_token.
I don't really understand how that can be, since I believe I am correctly sending the X-CSRF-Token header correctly with each ajax request.
Basically, my app authenticates and Devise sends back an auth_token and a csrf_token:
render :status => 200, :json => {
:auth_token => #user.authentication_token,
:csrf_token => form_authenticity_token
}
I then store those tokens in my ajax app, and using ajaxSend in jQuery, set it up so jQuery passes those tokens with each request:
initialize: ->
#bindTo $(document), 'ajaxSend', #appendTokensToRequest
appendTokensToRequest: (event, jqXHR, options) ->
if not #authToken? then return
if #csrfToken?
jqXHR.setRequestHeader 'X-CSRF-Token', #csrfToken
if options.type is 'POST'
options.data = options.data + (if options.data.match(/\=/) then '&' else '') +
$.param auth_token:#authToken
else
options.url = options.url + (if options.url.match(/\?/) then '&' else '?') +
$.param auth_token:#authToken
I can then see in the chrome network tab, that for each GET request the auth_token param is being sent, as well as the X-CSRF-Token header.
On PUT requests however it doesn't seem to be working though.
My theory is that CORS is stuffing things up. If you make a CORS request, your browser actually makes an additional OPTIONS request first just to check that you do have permission to access this resource.
I suspect that it is the OPTIONS request which is not passing the X-CSRF-Token header, thus rails immediately invalidates the csrf_token on the rails end. Then when jQuery makes the actual PUT request the csrf_token it passes is no longer valid.
Could this be the problem?
What can I do to prove that? Chrome doesn't seem to show me the OPTIONS requests in the network tab to help me debug the issue.
It's not a major issue, because I can just turn the CSRF stuff off. But I'd like to know why it's not working.
I think you'll need to handle the OPTIONS request, which should respond with the various headers that will allow the CORS request, IIRC they are the access-control-allow-method, access-control-allow-origin and access-control-allow-headers. Because the OPTIONS request is failing, the PUT request probably isn't occurring.
I just ran into the same issue. The problem is that the _session_id cookie cannot be sent in CORS. As a result, when Rails tries to verify the token, the session[:_csrf_token] is null and Rails generates a new one before comparison.
To solve the issue, you need to enable cookie sending in CORS. Here is the Mozilla Developer Network reference. Work is needed on both the server and client side to make it work.
Client
- Refer to your client technologies document.
Server
- Set the header Access-Control-Allow-Credentials to true (string) in the response to the preflight (HTTP OPTIONS) call.
In Rails every form submission need CSRF token authenticity.
It use to submit form securely.
The CSRF token(each time) will create newly in rails when we open our Application.
If the CSRF token not passing inside our controller this WARNING will show.
We need to pass this token in all form submissions.
I have this ajax call after completing specific action
$.ajax({
type: "POST",
url: $("#myform").prop('action'),
data: $("#myform").serialize(),
xhrFields: {withCredentials: true},
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').last().attr('content'));
}
});
this ajax call refering to create action, so I wanna save the params of the form in the session I've created before. so I put this in create action
def create
session[:my_session].deep_merge!(params[:user])
puts session[:my_session]
I print the session to make sure from the server that it contains the right params, and Yeah, it has been printed with the right values.
{"profile_id"=>"1000", "first_name"=>"john", "last_name"=>"vieira", "email"=> "john#sandsora.com"}
the problem here is after that call, the session doesn't save the data which I've stored!, which means that it stored the latest data before this assignment. I searched for that error and I got that it may be CSRF token error and I fixed it by adding beforeSend function, but I still have the same problem, so any suggestions please?
My first guess would be that the session cookie headers are not being set. Without this cookie rails has not way of setting the correct session and you data is "lost" after the request has completed. Your browser should send it automatically on your ajax requests though...
Can you verify that the session cookie is being sent with your ajax request?