How to protect against CSRF when using Backbone.js to post data? - javascript

Backbone.js handles posting data to server under the hood, so there is no easy way to insert a CSRF token in the payload. How can I protect my site against CSRF in this situation?
In this SO answer: https://stackoverflow.com/a/10386412/954376, the suggestion is to verify the x-Requested-By header to be XMLHTTPRequest. Is this enough to block all CSRF attempts?
In Django docs, the suggestion is to add CSRF token in another custom header in every AJAX request: https://docs.djangoproject.com/en/1.5/ref/contrib/csrf/#ajax. Is this necessary?
I understand if the attack uses hidden form, I am safe by just assuring the request is from XMLHTTPRequest. But is there any CSRF attack tricks that can forge the header?

Setting a global CSRF-token for all jQuery.ajax calls:
$(function(){
$.ajaxSetup({
headers: {'X-CSRFToken': CSRF_TOKEN}
});
})
Setting the token just for Backbone by overriding Backbone.sync:
var oldSync = Backbone.sync;
Backbone.sync = function(method, model, options){
options.beforeSend = function(xhr){
xhr.setRequestHeader('X-CSRFToken', CSRF_TOKEN);
};
return oldSync(method, model, options);
};
EDIT: Fixed a typo Kadam points at in comments

You can use a prefilter to add the token to all requests:
$.ajaxPrefilter(function(opts) {
if (opts.data) {
opts.data += "&";
}
opts.data += "csrfToken=" + token;
});
You may need to add additional logic if you don't always send the token.

Here's an updated version, based in Django 1.7 (using the jQuery cookie plugin)
oldSync = Backbone.sync
Backbone.sync = (method, model, options) ->
csrfSafeMethod = (method) ->
# these HTTP methods do not require CSRF protection
/^(GET|HEAD|OPTIONS|TRACE)$/.test method
options.beforeSend = (xhr, settings) ->
if !csrfSafeMethod(settings.type) and !#crossDomain
xhr.setRequestHeader 'X-CSRFToken', $.cookie('csrftoken')
return
oldSync method, model, options

I know it's a bit old question, but I'll leave a link to the github repo of AMD module just for this:
https://github.com/kuc2477/backbone.csrf.git (disclaimer: I'm the author of the module)

Related

How to send client side cookies (javascript) to server side (node.js) using Microsoft Bot Framework Directline API? [duplicate]

I am working on an internal web application at work. In IE10 the requests work fine, but in Chrome all the AJAX requests (which there are many) are sent using OPTIONS instead of whatever defined method I give it. Technically my requests are "cross domain." The site is served on localhost:6120 and the service I'm making AJAX requests to is on 57124. This closed jquery bug defines the issue, but not a real fix.
What can I do to use the proper http method in ajax requests?
Edit:
This is in the document load of every page:
jQuery.support.cors = true;
And every AJAX is built similarly:
var url = 'http://localhost:57124/My/Rest/Call';
$.ajax({
url: url,
dataType: "json",
data: json,
async: true,
cache: false,
timeout: 30000,
headers: { "x-li-format": "json", "X-UserName": userName },
success: function (data) {
// my success stuff
},
error: function (request, status, error) {
// my error stuff
},
type: "POST"
});
Chrome is preflighting the request to look for CORS headers. If the request is acceptable, it will then send the real request. If you're doing this cross-domain, you will simply have to deal with it or else find a way to make the request non-cross-domain. This is why the jQuery bug was closed as won't-fix. This is by design.
Unlike simple requests (discussed above), "preflighted" requests first
send an HTTP request by the OPTIONS method to the resource on the
other domain, in order to determine whether the actual request is safe
to send. Cross-site requests are preflighted like this since they may
have implications to user data. In particular, a request is
preflighted if:
It uses methods other than GET, HEAD or POST. Also, if POST is used to send request data with a Content-Type other than
application/x-www-form-urlencoded, multipart/form-data, or text/plain,
e.g. if the POST request sends an XML payload to the server using
application/xml or text/xml, then the request is preflighted.
It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)
Based on the fact that the request isn't sent on the default port 80/443 this Ajax call is automatically considered a cross-origin resource (CORS) request, which in other words means that the request automatically issues an OPTIONS request which checks for CORS headers on the server's/servlet's side.
This happens even if you set
crossOrigin: false;
or even if you ommit it.
The reason is simply that localhost != localhost:57124. Try sending it only to localhost without the port - it will fail, because the requested target won't be reachable, however notice that if the domain names are equal the request is sent without the OPTIONS request before POST.
I agree with Kevin B, the bug report says it all. It sounds like you are trying to make cross-domain ajax calls. If you're not familiar with the same origin policy you can start here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Same_origin_policy_for_JavaScript.
If this is not intended to be a cross-domain ajax call, try making your target url relative and see if the problem goes away. If you're really desperate look into the JSONP, but beware, mayhem lurks. There really isn't much more we can do to help you.
If it is possible pass the params through regular GET/POST with a different name and let your server side code handles it.
I had a similar issue with my own proxy to bypass CORS and I got the same error of POST->OPTION in Chrome. It was the Authorization header in my case ("x-li-format" and "X-UserName" here in your case.) I ended up passing it in a dummy format (e.g. AuthorizatinJack in GET) and I changed the code for my proxy to turn that into a header when making the call to the destination. Here it is in PHP:
if (isset($_GET['AuthorizationJack'])) {
$request_headers[] = "Authorization: Basic ".$_GET['AuthorizationJack'];
}
In my case I'm calling an API hosted by AWS (API Gateway). The error happened when I tried to call the API from a domain other than the API own domain. Since I'm the API owner I enabled CORS for the test environment, as described in the Amazon Documentation.
In production this error will not happen, since the request and the api will be in the same domain.
I hope it helps!
As answered by #Dark Falcon, I simply dealt with it.
In my case, I am using node.js server, and creating a session if it does not exist. Since the OPTIONS method does not have the session details in it, it ended up creating a new session for every POST method request.
So in my app routine to create-session-if-not-exist, I just added a check to see if method is OPTIONS, and if so, just skip session creating part:
app.use(function(req, res, next) {
if (req.method !== "OPTIONS") {
if (req.session && req.session.id) {
// Session exists
next();
}else{
// Create session
next();
}
} else {
// If request method is OPTIONS, just skip this part and move to the next method.
next();
}
}
"preflighted" requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
Consider using axios
axios.get( url,
{ headers: {"Content-Type": "application/json"} } ).then( res => {
if(res.data.error) {
} else {
doAnything( res.data )
}
}).catch(function (error) {
doAnythingError(error)
});
I had this issue using fetch and axios worked perfectly.
I've encountered a very similar issue. I spent almost half a day to understand why everything works correctly in Firefox and fails in Chrome. In my case it was because of duplicated (or maybe mistyped) fields in my request header.
Use fetch instead of XHR,then the request will not be prelighted even it's cross-domained.
$.ajax({
url: '###',
contentType: 'text/plain; charset=utf-8',
async: false,
xhrFields: {
withCredentials: true,
crossDomain: true,
Authorization: "Bearer ...."
},
method: 'POST',
data: JSON.stringify( request ),
success: function (data) {
console.log(data);
}
});
the contentType: 'text/plain; charset=utf-8', or just contentType: 'text/plain', works for me!
regards!!

how to make a proper ajax call in javascript to django framework with csrf protection?

before you mark this as duplicate please read through as i have gone through lots of stack overflow questions but could not find a suitable solution.
So the problem i am facing is i am new to django and learnt about CSRF protection for POST requests. I have successfully implemented these calls on a non-ajax based page. But the current project that i am working on is a one page application. So all the calls are through ajax and in vanila JS not using any library. The problem that i am facing with is that for the first request i get the valid CSRF token that i generated in the template. But after the first ajax call the CSRF token changes. So i want to know what is the right method in django for a situation like this. Should i make all request respond with CSRF token somehow and save them in a JS variable?
Also currently there are two pages. The first is a simple login template which has no ajax calls . It posts to the home page with credentials and if valid it is done. But inside the home there are multiple forms. And submitting any one of them changes the token so how do i handle a situation like this.
PS: i prefer the codes in pure JS not jquery or any other framework and would not want to disable to csrf protection.
I already though of storing the CSRF token in cookie or session variable by that would be defeating the whole purpose of token.
Please if you could attach a sample code that i can learn from.
The problem that i am facing with is that for the first request i get
the valid CSRF token that i generated in the template. But after the
first ajax call the CSRF token changes.
I don't believe this is true. Otherwise, any django application would stop working if the user opens more than one tab.
Here's the solution I use on my webapps. It works as expected, but I didn't follow the official recommendation of getting the token value from the cookie. Why? Less code.
myapp/templatetags/csrf_ajax.html
from django import template
register = template.Library()
#register.inclusion_tag('myapp/_csrf_ajax.html')
def csrf_ajax():
# https://docs.djangoproject.com/en/1.8/ref/csrf/#ajax
return {}
_csrf_ajax.html
<script>
(function () {
var csrf_token = "{{ csrf_token }}";
function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrf_token);
}
}
});
})();
</script>
Then I use this templatetag in each page that I want the CSRF ajax setup.
{% csrf_ajax %}
The csrttoken is stored in cookies and it doesn't change with every ajax request. I have this code in a js file and add it to any page that needs to send ajax post requests (it uses jQuery and cookie, you need to translate it to plain JavaScript if you don't want to use external libraries):
var csrftoken = $.cookie('csrftoken');
function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});

Preventing HTTP Basic Auth Dialog using AngularJS Interceptors

I'm building an AngularJS (1.2.16) web app with a RESTful API, and I'd like to send 401 Unauthorized responses for requests where authentication information is invalid or not present. When I do so, even with an HTTP interceptor present, I see the browser-presented basic "Authentication Required" dialog when an AJAX request is made via AngularJS. My interceptor runs after that dialog, which is too late to do something useful.
A concrete example:
My backend API returns 401 for /api/things unless an authorization token is present. Nice and simple.
On the AngularJS app side, I've looked at the docs and set up an interceptor like this in the config block:
$httpProvider.interceptors.push(['$q', function ($q) {
return {
'responseError': function (rejection) {
if (rejection.status === 401) {
console.log('Got a 401')
}
return $q.reject(rejection)
}
}
}])
When I load my app, remove the authentication token, and perform an AJAX call to /api/things (to hopefully trigger the above interceptor), I see this:
If I cancel that dialog, I see the console.log output of "Got a 401" that I was hoping to see instead of that dialog:
Clearly, the interceptor is working, but it's intercepting too late!
I see numerous posts on the web regarding authentication with AngularJS in situations just like this, and they all seem to use HTTP interceptors, but none of them mention the basic auth dialog popping up. Some erroneous thoughts I had for its appearance included:
Missing Content-Type: application/json header on the response? Nope, it's there.
Need to return something other than promise rejection? That code always runs after the dialog, no matter what gets returned.
Am I missing some setup step or using the interceptor incorrectly?
Figured it out!
The trick was to send a WWW-Authenticate response header of some value other than Basic. You can then capture the 401 with a basic $http interceptor, or something even more clever like angular-http-auth.
I had this issue together with Spring Boot Security (HTTP basic), and since Angular 1.3 you have to set $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'; for the popup not to appear.
For future reference
I've come up with this solution when trying to handle 401 errors.
I didn't have the option to rewrite Basic to x-Basic or anything similar, so I've decided to handle it on client side with Angular.
When initiating a logout, first try making a bad request with a fake user to throw away the currently cached credentials.
I have this function doing the requests (it's using jquery's $.ajax with disabled asynch calls):
function authenticateUser(username, hash) {
var result = false;
var encoded = btoa(username + ':' + hash);
$.ajax({
type: "POST",
beforeSend: function (request) {
request.setRequestHeader("Authorization", 'Basic ' + encoded);
},
url: "user/current",
statusCode: {
401: function () {
result = false;
},
200: function (response) {
result = response;
}
},
async: false
});
return result;
}
So when I try to log a user out, this happens:
//This will send a request with a non-existant user.
//The purpose is to overwrite the cached data with something else
accountServices.authenticateUser('logout','logout');
//Since setting headers.common.Authorization = '' will still send some
//kind of auth data, I've redefined the headers.common object to get
//rid of the Authorization property
$http.defaults.headers.common = {Accept: "application/json, text/plain, */*"};

AngularJS: Cannot send POST request with appropiate CORS headers

I'm creating a web app using AngularJS. To test it, I'm running the app in a NodeJS server, using angular-seed template.
In this app, I need to send a JSON message to another host, via POST request, and get the response, so, I'm using CORS.
My request is done by implementing a service that uses AngularJS http service (I need the level of abstraction that $http provides. So, I don't use $resource).
Here, my code. Please pay attention to the fact that I modify $httpProvider to tell AngularJS to send its requests with the appropriate CORS headers.
angular.module('myapp.services', []).
// Enable AngularJS to send its requests with the appropriate CORS headers
// globally for the whole app:
config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
/**
* Just setting useXDomain to true is not enough. AJAX request are also
* send with the X-Requested-With header, which indicate them as being
* AJAX. Removing the header is necessary, so the server is not
* rejecting the incoming request.
**/
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
]).
factory('myService', function($http) {
return {
getResponse: function() {
var exampleCommand = JSON.stringify({"foo": "bar"});
// This really doesn't make a difference
/*
var config = {headers: {
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Headers': 'Content-Type, Content-Length, Accept',
'Content-Type': 'application/json'
}
};
*/
//return $http.post(REMOTE_HOST, exampleCommand, config).
return $http.post(REMOTE_HOST, exampleCommand).
success(function(data, status, headers, config) {
console.log(data);
return data;
}).
error(function (data, status, headers, config) {
return {'error': status};
});
}
}
});
The problem is I can't make it work. I always get this error message:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at REMOTE_HOST. This can be fixed by moving the
resource to the same domain or enabling CORS.
But if I do a simple jQuery AJAX call like this:
$.ajax(REMOTE_HOST,
{
dataType: "json",
type: "POST",
data: exampleCommand,
success: function(data) { console.log(data); },
error: function(request, textStatus, errorThrown) { console.log("error " + textStatus + ": " + errorThrown);}
});
It works fine.
So, my questions:
- How do I allow cross-site requests in an AngularJS running under NodeJS?
UPDATE: Thanks to Dayan Moreno Leon's response.
My problem is I need to add cors support to my server. I'm using NodeJS http-server for development and lighttpd for production.
- Why does the simple jQuery POST request work but AngularJS POST request doesn't?
I guess jQuery AJAX requests are cross-domain by default. Not really sure yet.
Many thanks in advance
CORS is not handled on the client but in the server you need to allow CORS on your nodejs app where your angular app is trying to POST. you can try using cors module if you are using express
https://www.npmjs.org/package/cors
other whise you need to check for the options method and return 200 as a response
http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
Why does the simple jQuery POST request work but AngularJS POST request doesn't?
jQuery uses simple requests while AngularJS uses preflighted requests
In your angular code you can add set Content-Type to application/x-www-form-urlencoded and encode your data using $.param

Add access_token in backbone.js

In my REST server, it's requiring the access_token to be present in every request. i.e. in POSTing data, access_token needs to be submitted together with the attributes.
How do I configure backbone.js to add access_token to every GET, PUT, POST and DELETE request?
Thanks.
Okay, I think I found a way how to do it in jQuery.
$.ajaxSetup (
{
data: { access_token: 'my_access_token' }
}
);
Backbone uses jQuery/Zepto for AJAX requests, so you can use the functionality available in those libraries.
To add custom headers to all XHR calls made by jQuery, you can use the jQuery.ajaxSend event, which is triggered before every ajax request, and modify the jqXHR it receives as an argument.
Edit based on OP's comments:
Probably the simplest way to modify the sent data is to override the Backbone.sync function. You could wrap the native implementation, and add the required property there:
var nativeSync = Backbone.sync;
Backbone.sync = function (method, model, options) {
//for POST/PUT requests, add access token to the request
if(model && (method === 'create' || method === 'update')) {
var data = _.extend(model.toJSON(), {
access_token: 'token'
});
options.data = JSON.stringify(data);
}
//call the native Backbone.sync implementation
nativeSync(method, model, options);
};

Categories

Resources