This murder is as good as solved.
For the past half year or so I've been using jQuery exclusively in combination with a Rails backend.
It started when I read an article over at errtheblog. If you haven't read it yet you might want to do so.
Aside note: I didn't find the jQuery Form Plugin useful after all, I found I could usually do without. The corner plugin turned out to be extremely buggy, all versions of them (the old September 2007 version was the least problematic), and only works for really simple cases.
Additional jQuery plugins that I can wholeheartedly recommend are definitely LiveQuery, jsHotKeys (new version released today), metadata and jGrowl.
Anyways, the one thing that is not explained in the errtheblog article is how to get around the issue with rails usage of authenticity tokens. Their site got around that, I assume, by simply disabling the check for the authenticity token in their controllers. I wanted to keep this check enabled though as I think it's quite useful (case in point: see mislavs excercise in cross-site request forgery). The following explains how I did that.
In your application_helper.rb:
# Passes the authenticity token for use in javascript
def yield_authenticity_token
if protect_against_forgery?
<<JAVASCRIPT
<script type='text/javascript'>
//<![CDATA[
window._auth_token_name = "#{request_forgery_protection_token}";
window._auth_token = "#{form_authenticity_token}";
//]]>
</script>
JAVASCRIPT
end
end
In your layouts header somewhere:
= yield_authenticity_token
In your application.js:
// Behaviours
$(document).ready(function() {
// All non-GET requests will add the authenticity token
// if not already present in the data packet
$("body").bind("ajaxSend", function(elm, xhr, s) {
if (s.type == "GET") return;
if (s.data && s.data.match(new RegExp("\\b" + window._auth_token_name + "="))) return;
if (s.data) {
s.data = s.data + "&";
} else {
s.data = "";
// if there was no data, jQuery didn't set the content-type
xhr.setRequestHeader("Content-Type", s.contentType);
}
s.data = s.data + encodeURIComponent(window._auth_token_name)
+ "=" + encodeURIComponent(window._auth_token);
});
});
// All ajax requests will trigger the wants.js block
// of +respond_to do |wants|+ declarations
jQuery.ajaxSetup({
'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")},
});
So far you got the crucial bits.
Now for some added sugar, add more to application.js:
// More behaviours
$(document).ready(function() {
// All A tags with class 'get', 'post', 'put' or 'delete' will perform an ajax call
$('a.get').livequery('click', function() {
$.get($(this).attr('href'));
return false;
}).attr("rel", "nofollow");
['post', 'put', 'delete'].forEach(function(method) {
$('a.' + method).livequery('click', function() {
if ($(this).metadata().confirm &&
!confirm($(this).metadata().confirm)) return false;
$.post($(this).attr('href'), "_method=" + method);
return false;
}).attr("rel", "nofollow");
});
});
Note: forEach is a JavaScript extension to the ECMA-262 standard from Mozilla. See here for more info.
In your view:
<a href='/post/12345/rate/4' class='put'>I'm giving this a 4!</a>
Whenever a user clicks on that link it will trigger an ajax call making a PUT request using the URL that is mentioned by the href attribute.
Handle such requests in your controller:
# PUT /post/:id/rate/:rating
def rate
respond_to do |wants|
wants.js do
Post.find(params[:id]).rate(params[:rating].to_i, current_user)
head :ok
end
end
end
http://ennerchi.com/projects/jrails seems to handle all of this out of the box. It's a nice plugin.
— Psynix on September 5, 2008 at 2:28 am #
It's a nice plugin, but it only works if you use the remote_function method of rails. Which I never will. It still doesn't allow you make for example a PUT request from your application.js javascript code.
The whole point of unobtrusive javascript programming is that you separate the concerns of HTML, CSS and Javascript. Using those rails methods doesn't help as it rather obtrusively injects all kinds of javascript code in your HTML. Once you keep your concerns really separated you will find yourself at a much more beautiful place than those rails methods provide.
— Lawrence Pit on September 5, 2008 at 4:42 am #
Nice writeup!
But ... what are the "rel=nofollow" bits?
— Mislav on September 5, 2008 at 6:23 am #
Thanks!that's I need, the jrails is not unobtrusive.
— Dave on September 5, 2008 at 9:09 am #
jQuery is the best thing I've found since Rails. I kick myself for not working with it
earlier.
I don't agree with the way rails uses in-line JS and its RJS templates.
Just with more people would see the light
---
http://thenexttrain.co.za/2008/08/screencast-ruby-on-rails-unobtrusive-jquery/
Screencast basics for anyone who wants to see just how easy jquery is.
— Neil Henegan on September 5, 2008 at 12:30 pm #
Hi,
i don't really get the code of the authenticity_token stuff. What are the advantage over just sending the auth-token with all requests? So just say this (in application.js):
$.ajaxSetup({ data: { authenticity_token : AUTH_TOKEN }}) // or window._auth_token
It works for me. Is this a security issue?
— Andreas on September 10, 2008 at 6:41 pm #
I have been using this method of including the auth token in the javascript, which works well. Now I have changed to the tactic of including a form in my html for all ajax requests I might make. In this case you just need one hidden form.link_form in the bottom of your document. Then, in your 'ajaxSend' filter, you can say
s.data = s.data + '&' + $('form.link_form').serialize();
This seems cleaner to me and leaves the responsibility for the formats entirely in Rails. The rails code to create the form looks like
'/some/route', :html => { :class => 'hidden link_form' } do |f| %>
— Austin Putman on September 10, 2008 at 6:43 pm #
the rails code got eaten by formatting, but you can see the gist here: http://gist.github.com/9944
— Austin Putman on September 10, 2008 at 6:46 pm #
[...] (authenticity tokens) в ваших AJAX-запросах. Lawrence Pit описал код jQuery, который вам необходим чтобы избежать дальнейших [...]
— Блог разработчика веба » Архив блога » Неделя на Рельсах (10 сентября 2008 г.) on September 11, 2008 at 1:00 am #
I don't think it's necessary to send an authentication token at all with Ajax requests if you want to protect against CSRF attacks. Simple set the X-Requested-By: XMLHttpRequest heading in your Ajax requests (jQuery and Prototype both do this for you automatically) and have your server-side logic say "if the X-Requested-By: blah header is set, don't bother checking for a CSRF attack". This should be perfectly safe, since it's impossible to set arbitrary HTTP headers on a POST-based CSRF attack, which are the only ones you need to worry about (the same-origin policy prevents CSRF attacks being sent using Ajax APIs that have the ability to set that header).
Is there some other security benefit to sending the auth token with Ajax requests that I'm missing?
— Simon Willison on September 11, 2008 at 2:42 am #
@Simon: I believe you're right, but without patching the backend we have no choice but to comply and send the token.
@Lawrence: I've commented earlier, but my comment was blocked. Can you look into it?
— Mislav on September 11, 2008 at 5:33 pm #
@Andreas You're right. The only thing is that you'd have to be aware that e.g. $.post("/todo", "_method=put"); won't work, you'd have to do $.post("/todo", {_method:"put"}); instead, i.e. always use a hash instead of a string when you send the data. This might trip up other developers or my future self when adding a new $.post in the future.
@Mislav The rel=nofollow bits are added to tell bots not to follow the links that are supposed to be used for AJAX requests only. Not entirely necessary, but I find it useful.
@Simon I wouldn't trust browser implementations that much. I believe IE browsers can perform XHR requests to outside domains.
— Lawrence Pit on September 12, 2008 at 1:34 am #
Thanks for this solution. I've been guiltily skipping the authenticity token verification in my controllers, but now I can stop cutting that corner.
— Andy Stewart on September 12, 2008 at 2:47 pm #
This is what I did: http://henrik.nyh.se/2008/05/rails-authenticity-token-with-jquery
— Henrik N on September 12, 2008 at 3:35 pm #
New to Rails ... I must be doing something wrong. I get error in the application_helper.rb:
application_helper.rb:78: can't find string "JAVASCRIPT" anywhere before EOF
application_helper.rb:10: syntax error, unexpected $end, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END
— Dnew on September 12, 2008 at 7:29 pm #
[...] Unobtrusive JQuery from Scratch [...]
— เร็วส์ หกสิบหก » Blog Archive » นั่งเทียนเขียนข่าว#4 on September 12, 2008 at 10:07 pm #
It would be of amazing value to see the whole process front to back in a mini tutorial. For example, if I have a simple edit method in my controller, and in http it displays the edit.html.erb template and responds to SAVE/CANCEL. I would love to see a jquery unobtrusive example which instead uses say the facebox/lightbox plug and displays the edit template inside of it (without leaving page of course). And all events/method are handled ...SAVE/DELETE and the parent partial is updated in the dom. Anyone wanna take a shot?
— Dnew on September 12, 2008 at 11:31 pm #
Dnew, an 'end' statement is missing after 'JAVASCRIPT' in the above snippet. Add that and move 'JAVASCRIPT' to the extreme left (there shouldn't be any space before that) and it should work fine.
— Subbu on September 14, 2008 at 11:38 am #
[...] http://blog.lawrencepit.com/2008/09/04/unobtrusive-jquery-rails/ Read More [...]
— roadburn’s sharepoint 2007 blog » Blog Archive » Using JQuery on Rails on September 16, 2008 at 4:42 am #
[...] หน้านี้ ซึ่งเป็นการเอา jQuery มาใช้ในการเรียก [...]
— เร็วส์ หกสิบหก » Blog Archive » วิธีการนำ jQuery มาใช้ใน Rails โดยไม่ให้มีผลกระทบกับ Rails on September 18, 2008 at 9:21 am #
I'm not sure this technique will work against CSRF.
Js is visible to the user who can forge a request with the same token.
Any solution to obfuscate the token to the user?
— Marco on July 6, 2009 at 12:06 pm #
Hi! I was surfing and found your blog post... nice! I love your blog.
Cheers! Sandra. R.
— sandrar on September 11, 2009 at 12:50 am #
[...] you Lawrence Pit for this [...]
— Ruby on Rails: Danger! Cross Site Request Forgery | The Next Train on September 30, 2009 at 10:56 pm #
@Marco - CSRF is about a malicious site using your credentials from another site without your knowledge, that's where the "cross-site" part comes in. The issue of user's forging requests for their own credentials is a more basic security issue, which is that you can never trust any user input in any program (web or otherwise) ever. Obfuscating the auth_token has exactly zero security value. To ensure the user is not doing anything naughty with their own access, you must restrict that access on the server side. If you are depending on client-side security you have already failed.
— Gabe da Silveira on November 10, 2009 at 1:35 am #