Reputation: 5848
My APIs are running in api.domain.com
and frontend angular application
is running under frontend.domain.com
CSRF implementation
api.domain.com
and access allowed only from frontend.domain.com
.X-CSRFTOKEN
In Angular app, first it will make a request to get the CSRF token and
attach it with the request header for every post
request
//get CSRF token
$http.get("http://localhost:3000/").then(function (response) {
console.log(response.headers('X-CSRFTOKEN'));
var request = {
method: "POST",
url: 'http://localhost:3000/csrftest',
headers: {
"CSRF-Token": response.headers('X-CSRFTOKEN')
}
}
//post request with CSRF token in header
$http(request).then(function (res) {
console.log(res);
}, function (err) {
console.log(err);
})
}, function (err) {
console.log("Error", err);
});
One drawback is, I need to get CSRF token for every request so there is an extra $http
call needed for every request because the CSRF token is changing every request (is there anyway to overcome this extra http call?)
My question is it the proper way to adding the CSRF protection in Cross-domain applications??
Upvotes: 3
Views: 3500
Reputation: 25797
In my view (or as I saw as a general practice), at the start of the application, you should do the CSRF call once and your server should set a cookie containing the CSRF token (so you need to modify your server code to generate a single CSRF token for the current user session) which should be valid for the whole user session.
Then, in your Angular application, define the request interceptor, and you can read that cookie and set it as HTTP header for every POST request.
Read more here: https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token
For example: (using your existing code of passing the token in response header)
Assuming, your server uses the same CSRF token everytime for the existing session.
In your run
block, hit the API and get the token and store that in the localStorage
:
angular.module('myApp').run(function ($http) {
// This will be executed once the application loads
$http.get('http://localhost:3000/').then(function (response) {
// store the token in the local storage for further use
localStorage.csrfToken = response.headers('X-CSRFTOKEN');
});
});
Then add an interceptor and modify the POST requests to add that token:
angular.module('myApp').config(function ($httpProvider) {
$httpProvider.interceptors.push(function() {
return {
'request': function(config) {
if (config.method === 'POST') {
config.headers['CSRF-Token'] = localStorage.csrfToken;
}
return config;
}
};
});
});
Now your every POST request will be intercepted and the CSRF token will be set.
But if you want a DRY code where you want to do an extra HTTP call for getting CSRF token before every POST request, that is also possible either using interceptors or a service.
angular.module('myApp').config(function ($httpProvider) {
$httpProvider.interceptors.push(function($http, $q) {
function doAnotherCallToGetTokenAndSetItToRequest(config) {
var defer = $q.defer();
$http.get("http://localhost:3000/").then(function (response) {
config.headers['CSRF-Token'] = response.headers('X-CSRFTOKEN');
defer.resolve(config);
});
return defer.promise;
}
return {
'request': function(config) {
if (config.method === 'POST') {
return doAnotherCallToGetTokenAndSetItToRequest(config);
}
return config;
}
};
});
});
From the docs:
request
: interceptors get called with a httpconfig
object. The function is free to modify theconfig
object or create a new one. The function needs to return theconfig
object directly, or a promise containing theconfig
or a new config object.
Now, anywhere in your code, just call API's directly and the interceptor will take care of pulling a new CSRF token and setting it to the current request:
var request = {
method: "POST",
url: 'http://localhost:3000/csrftest'
}
$http(request).then(function (res) {
console.log(res);
}, function (err) {
console.log(err);
})
Upvotes: 2