1 minute, 56 seconds
I was working on implemented a nonce to fight CSRF requests on all our forms. The nonce worked liked you expect: by setting a cookie and then putting the same value in the form. When the form was submitted, the back end checked that the form nonce matched the cookie nonce and, bam, bob is your uncle.
The HTML for the form was hosted on foo.domain.com and was fetched via an AJAX call to render it in a modal on bar.domain.com. It used a JavaScript function that looked like this:
$('.login_action').click(function(){
Modal.init({
id: "login",
primary_button_action: function(){
$('form #loginform').submit();
},
url: URL_ROOT + "modal/login",
}).open();
});
Now, you can’t quite tell, but this is ultimately a wrapper for Twitter’s Bootstrap’s Modal call:
$('#login').modal(options)
For the life of me I could not figure out when the AJAX call from foo subdomain to bar subdomain was not setting the nonce cookie. I thought it would be a CORS issue, but quickly dismissed that because we all know that “AJAX Requests Get And Set Cookies Like Any Other HTTP Request“, right. RIGHT?!!?
I did follow the best practice of putting in poor man’s PHP break points to trace the $_POST and $_COOKIE values, but to no avail.
After far too long, but after a healthy discussion with a friend about how we learn and how we trouble shoot, I remembered my mantra I always tell junior engineers: “Simplify the problem! Distill it down. Ideally, continue to cut the code in half again and again until just the bare minimum manifesting the problem remains.”
This lead me to reproduce the problem in two lines of code on foo.domain.com to call bar.domain.com:
The problem was still there! No nonce cookies getting set. Let’s remove jQuery then and go to raw JS requests:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://bar.domain.com', true);
xhr.withCredentials = true;
xhr.send();
Wait ah second! What was that I see in the console?!@@!#%$:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://bar.domain.com. (Reason: CORS header 'Access-Control-Allow-Origin' does not match '*').
Oh…CORS…the thing I dismissed upfront. So, yes, AJAX calls are just HTTP requests per bennadel.com’s blog post, but of course if foo subdomain is calling the modal on bar subdomain via AJAX, it’s an XHR request to another (sub)domain which needs to play by CORS rules.
If any one is interested, I published a gist with our PHP code to do nonces.