Many AJAX requests at once with CSRF protection - javascript

Hi everybody.
My web application is based on asynchronous requests. Timer widget is working and updating it's status every second by AJAX (yes, it is necessary).
I am sending with each AJAX my CSRF tokens:
project_data.append(csrf_name_key,csrf_name_value);
project_data.append(csrf_value_key,csrf_value_value);
And in response I am updating that global variables:
function setCSRF(response) {
csrf_name_key = response.nameKey;
csrf_name_value = response.name;
csrf_value_key = response.valueKey;
csrf_value_value = response.value;
}
Everything is generally fine. But if I will do another AJAX for example when I change task in todo list to "done" it sometimes ending with error because I am sending AJAX before I am getting new tokens from previous request.
I really don't know how to do solve that problem. First idea was that I will make "like stack array" with 5 different tokens but one https request = one pair of tokens and I can't generate it.
Maybe some type of queue of ajax requests, but what with doing them in a right time - I don't know.
My actual pseudo-solution is "if failed try again max 10 times":
if(e.target.response=="Failed CSRF check!") {
if(failedAjax<10) checkForSurvey();
failedAjax++;
return;
}
It is generally working, but errors are appears in a console and it is very dirty solution.
I am using Slim 3 microframework with CSRF extension. Really please for help with that interesting problem.
I will be very thankful,
Arthur

There are some options for you:
Use a stack of csrf-tokens inside you javascript code
Use a csrf token which is can be used more than once (not so secure)
Use a queue for the request
A stack for the tokens
The Slim-Csrf-middleware provides functionallity for you, to generate these tokens, you just need to get them to the clientside.
You could do an api for getting 5 csrf tokens, this api would also consume on csrf-token.
Add an api and generate the tokens there.
$app->get('/foo', function ($request, $response, $args) {
// check valid csrf token
$tokens = [];
for ($i = 0; $i < 5; $i++) {
$tokens[] = $this->csrf->generateToken();
}
return $response->withJson($tokens);
});
Now the csrf-token are valid through the whole user session.
Guard::generateToken() returns something like this:
array (size=2)
'csrf_name' => string 'csrf58e669ff70da0' (length=17)
'csrf_value' => string '52ac7689d3c6ea5d01889d711018f058' (length=32)
A multi-use csrf-token
For that, Slim-Csrf already provides functionallity with the token persistance mode. That can be enabled through the constructor or the Guard::setPersistentTokenMode(bool) method. In my example, I'm doing this with the method:
$container['csrf'] = function ($c) {
$guard = new \Slim\Csrf\Guard;
$guard->setPersistentTokenMode(true);
return $guard;
};
Here the PhpDoc from the persistanceTokenMode-attribute
/**
* Determines whether or not we should persist the token throughout the duration of the user's session.
*
* For security, Slim-Csrf will *always* reset the token if there is a validation error.
* #var bool True to use the same token throughout the session (unless there is a validation error),
* false to get a new token with each request.
*/
A queue for the ajax requests.
Add a queue for the request, that could be delay the execution of your request but there will always be a valid csrf token.
This should be seen as pseudocode as I havn't tested this yet.
var requestQueue = [];
var isInRequest = false;
var csrfKey = ''; // should be set on page load, to have a valid token at the start
var csrfValue = '';
function newRequest(onSuccessCallback, data) { // add all parameters you need
// add request to the queue
requestQueue.push(function() {
isInRequest = true;
// add to csrf stuff to data
$.ajax({
data: xxx
url: "serverscript.xxx",
success: function(data) {
// update csrfKey & csrfValue
isInRequest = false;
tryExecuteNextRequest(); // try execute next request
onSuccessCallback(data); // proceed received data
}
}});
);
tryExecuteNextRequest();
}
function tryExecuteNextRequest() {
if(!isInRequest && requestQueue.length != 0) { // currently no request running &
var nextRequest = requestQueue.shift();
nextRequest(); // execute next request
}
}

Generally, you can simply eliminate CSRF by not accepting cookies for authentication.
You can save the authentication token in the localStorage and send it as an header with each request.
This way you'll never need to worry about CSRF and its tokens.

Related

NodeJS - How to get cookies from server response

I want to use nodeJS as tool for website scrapping. I have already implemented a script which logs me in on the system and parse some data from the page.
The steps are defined like:
Open login page
Enter login data
Submit login form
Go to desired page
Grab and parse values from the page
Save data to file
Exit
Obviously, the problem is that every time my script has to login, and I want to eliminate that. I want to implement some kind of cookie management system, where I can save cookies to .txt file, and then during next request I can load cookies from file and send it in request headers.
This kind of cookie management system is not hard to implement, but the problem is how to access cookies in nodejs? The only way I found it is using request response object, where you can use something like this:
request.get({headers:requestHeaders,uri: user.getLoginUrl(),followRedirect: true,jar:jar,maxRedirects: 10,},function(err, res, body) {
if(err) {
console.log('GET request failed here is error');
console.log(res);
}
//Get cookies from response
var responseCookies = res.headers['set-cookie'];
var requestCookies='';
for(var i=0; i<responseCookies.length; i++){
var oneCookie = responseCookies[i];
oneCookie = oneCookie.split(';');
requestCookies= requestCookies + oneCookie[0]+';';
}
}
);
Now content of variable requestCookies can be saved to the .txt file and can loaded next time when script is executed, and this way you can avoid process of logging in user every time when script is executed.
Is this the right way, or there is a method which returns cookies?
NOTE: If you want to setup your request object to automatically resend received cookies on every subsequent request, use the following line during object creation:
var request = require("request");
request = request.defaults({jar: true});//Send cookies on every subsequent requests
In my case, i've used 'http'library like the following:
http.get(url, function(response) {
variable = response.headers['set-cookie'];
})
This function gets a specific cookie value from a server response (in Typescript):
function getResponseCookieValue(res: Response, param: string) {
const setCookieHeader = res.headers.get('Set-Cookie');
const parts = setCookieHeader?.match(new RegExp(`(^|, )${param}=([^;]+); `));
const value = parts ? parts[2] : undefined;
return value;
}
I use Axios personally.
axios.request(options).then(function (response) {
console.log(response.config.headers.Cookie)
}).catch(function (error) {
console.error(error)
});

Angular against Asp.Net WebApi, implement CSRF on the server

I'm implementing a website in Angular.js, which is hitting an ASP.NET WebAPI backend.
Angular.js has some in-built features to help with anti-csrf protection. On each http request, it will look for a cookie called "XSRF-TOKEN" and submit it as a header called "X-XSRF-TOKEN" .
This relies on the webserver being able to set the XSRF-TOKEN cookie after authenticating the user, and then checking the X-XSRF-TOKEN header for incoming requests.
The Angular documentation states:
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called XSRF-TOKEN on first HTTP GET request. On subsequent non-GET requests the server can verify that the cookie matches X-XSRF-TOKEN HTTP header, and therefore be sure that only JavaScript running on your domain could have read the token. The token must be unique for each user and must be verifiable by the server (to prevent the JavaScript making up its own tokens). We recommend that the token is a digest of your site's authentication cookie with salt for added security.
I couldn't find any good examples of this for ASP.NET WebAPI, so I've rolled my own with help from various sources. My question is - can anyone see anything wrong with the code?
First I defined a simple helper class:
public class CsrfTokenHelper
{
const string ConstantSalt = "<ARandomString>";
public string GenerateCsrfTokenFromAuthToken(string authToken)
{
return GenerateCookieFriendlyHash(authToken);
}
public bool DoesCsrfTokenMatchAuthToken(string csrfToken, string authToken)
{
return csrfToken == GenerateCookieFriendlyHash(authToken);
}
private static string GenerateCookieFriendlyHash(string authToken)
{
using (var sha = SHA256.Create())
{
var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(authToken + ConstantSalt));
var cookieFriendlyHash = HttpServerUtility.UrlTokenEncode(computedHash);
return cookieFriendlyHash;
}
}
}
Then I have the following method in my authorisation controller, and I call it after I call FormsAuthentication.SetAuthCookie():
// http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks
// http://docs.angularjs.org/api/ng.$http
private void SetCsrfCookie()
{
var authCookie = HttpContext.Current.Response.Cookies.Get(".ASPXAUTH");
Debug.Assert(authCookie != null, "authCookie != null");
var csrfToken = new CsrfTokenHelper().GenerateCsrfTokenFromAuthToken(authCookie.Value);
var csrfCookie = new HttpCookie("XSRF-TOKEN", csrfToken) {HttpOnly = false};
HttpContext.Current.Response.Cookies.Add(csrfCookie);
}
Then I have a custom attribute which I can add to controllers to make them check the csrf header:
public class CheckCsrfHeaderAttribute : AuthorizeAttribute
{
// http://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc
protected override bool IsAuthorized(HttpActionContext context)
{
// get auth token from cookie
var authCookie = HttpContext.Current.Request.Cookies[".ASPXAUTH"];
if (authCookie == null) return false;
var authToken = authCookie.Value;
// get csrf token from header
var csrfToken = context.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();
if (String.IsNullOrEmpty(csrfToken)) return false;
// Verify that csrf token was generated from auth token
// Since the csrf token should have gone out as a cookie, only our site should have been able to get it (via javascript) and return it in a header.
// This proves that our site made the request.
return new CsrfTokenHelper().DoesCsrfTokenMatchAuthToken(csrfToken, authToken);
}
}
Lastly, I clear the Csrf token when the user logs out:
HttpContext.Current.Response.Cookies.Remove("XSRF-TOKEN");
Can anyone spot any obvious (or not-so-obvious) problems with that approach?
Your code seems to be fine. The only thing is, you don't need most of the code you have as web.api runs "on top" of asp.net mvc, and latter has built in support for anti-forgery tokens.
In comments dbrunning and ccorrin express concerns that you only able to use build in AntiForgery tokens only when you are using MVC html helpers. It is not true. Helpers can just expose session based pair of tokens that you can validate against each other. See below for details.
UPDATE:
There is two methods you can use from AntiForgery:
AntiForgery.GetTokens uses two out parameters to return cookie token and form token
AntiForgery.Validate(cookieToken, formToken) validates if pair of tokens is valid
You totally can repurpose those two methods and use formToken as headerToken and cookieToken as actual cookieToken. Then just call validate on both within attribute.
Another solution is to use JWT (check eg MembershipReboot implementation)
This link shows how to use built in anti-forgery tokens with ajax:
<script>
#functions{
public string TokenHeaderValue()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
$.ajax("api/values", {
type: "post",
contentType: "application/json",
data: { }, // JSON data goes here
dataType: "json",
headers: {
'RequestVerificationToken': '#TokenHeaderValue()'
}
});
</script>
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
Also take a look at this question AngularJS can't find XSRF-TOKEN cookie
This solution isn't secure since CSRF attacks are still possible as long as the Auth cookie is valid. Both the auth and the xsrf cookie will be sent to the server when an attacker makes you perform a request via another site, and therefore you are still vulnerable until the user does a "hard" logout.
Each request or session should have its own unique token to truly prevent CRSF attacks. But probably the best solution is to not use cookie based authentication but token based authentication such as OAuth. This prevents other websites from using your cookies to perform unwanted requests, since the tokens are used in http headers instead of cookies. And http headers are not automatically send.
Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
AngularJS Token Authentication using ASP.NET Web API 2, Owin, and Identity
These excellent blog posts contain information of how to implement OAuth for WebAPI. The blog posts also contains great information of how to integrate it with AngularJS.
Another solution might be to disable CORS and only accept incoming requests from whitelisted domains. However this won't work for non-website applications, such as mobile and/or desktop clients. Next to that once your website is vulnerable to a XSS attack the attacker will still be able to forge requests on your behalve.
I think your code is flawed. The whole idea around prevent CSRF is to prevent a unique token on each REQUEST, not each session. If the anti-forgery token is a session persisted value, the ability to perform CSRF still remains. You need to provide a unique token on each request...
Haven't had any problems pointed out with the code, so I consider the question answered.

ExtJS 4.1 or general Javascript HTTP_Authorization Function

When making a request using curl --basic --user someuser:somepass http://someurl/ it append a header like this: Authorization: Basic Y2FsaWRvZzpmMDBmM2IwMg==. This is, of course,
Basic access authentication is a method for a web browser or other client program to provide a user name and password when making a request. Before transmission, the user name is appended with a colon and concatenated with the password. The resulting string is encoded with the Base64 algorithm and transmitted in the HTTP header and decoded by the receiver, resulting in the colon-separated user name and password string. Basic access authentication was originally defined in 1996 by RFC 1945 [http://en.wikipedia.org/wiki/Basic_access_authentication][1] [1]: http://en.wikipedia.org/wiki/Basic_access_authentication
I'm looking for a way to have all my ExtJS 4.1 Ext.data.proxy.Rest proxies add this to all requests. It seems like a simple task, but I can find no documentation on it. By the way, I do know how to add headers to proxies generally through headers: {'X_MyHeader':'somevalue'} property. I just do not know how to tell Ext to do it globally for the current username/password.
I was looking for a similar solution for applying OAuth tokens to my Ext.data.proxy.Rest proxies and couldn't find any info about it. Quite shocked there is nothing out there about how to do this. Makes me think I'm going about it the wrong way. Anyway heres what I've come up with to achieve this.
Override Ext.data.proxy.Ajax and keep a static variable so it applies to all proxies. Then merge the set authHeader with the current list of headers so you can still customise per proxy.
Ext.override(Ext.data.proxy.Ajax, {
statics: {
authHeader: undefined,
},
/**
* #cfg {Object} headers
* Any headers to add to the Ajax request. Defaults to undefined.
*/
doRequest: function(operation, callback, scope) {
var writer = this.getWriter(),
request = this.buildRequest(operation, callback, scope);
if (operation.allowWrite()) {
request = writer.write(request);
}
console.log(this.headers);
Ext.apply(request, {
headers : Ext.apply(this.headers, Ext.data.proxy.Ajax.authHeader) || Ext.data.proxy.Ajax.authHeader,
timeout : this.timeout,
scope : this,
callback : this.createRequestCallback(request, operation, callback, scope),
method : this.getMethod(request),
disableCaching: false // explicitly set it to false, ServerProxy handles caching
});
Ext.Ajax.request(request);
return request;
}
});
Then I look for my token in local storage, if it's there set it globally, otherwise prompt for a login.
MyApp.model.AuthToken.load(1, {
callback: function(record) {
// If we don't have anything in local storage for the user, show the login box.
if (record.get('access_token') == '') {
var window = Ext.create('MyApp.view.Login');
window.show();
} else {
Ext.data.proxy.Ajax.authHeader = { 'Authorization': 'Bearer ' + record.get('access_token') };
}
}
});
There should be some extra logic in there for handling token expiry and refresh requests, but you get the idea!

MVC4 Razor. How to pass a javascript variable to server?

I have a javascript variable that I want to pass back to the server side, which I thereafter intend to use it as an access token to grant user access to other pages which requires this token.
I wonder how do I pass this javascript variable back to server, so I can set it to a session variable? Do I need to send it back using ajax?
this is the part of jQuery I use to retrieve the token from server
$(document).ready(function () {
$('#loginForm').submit(function(e) {
var blargh = $(this).find('input').serialize();
$.ajax({
type: 'post',
url: '/WebAPI/api/authenticate/login',
data: blargh,
success: function (data) {
$.each(data, function(index, token) {
$('#container').prepend('<input type="hidden" name="MY_HIDDEN_FIELD_NAME" id="MY_HIDDEN_FIELD_NAME" value="'+token+'">');
});
},
error: function(jqXHR, status, errorThrown) {
alert("Error " + status + "\nError Thrown" + errorThrown )
},
});
e.preventDefault();
});
});
To pass back an additional item in the AJAX POST, you could add it like this...
var blargh = $(this).find('input').serialize();
blargh.someItem = "value";
Bear in mind that this only works when the form is submitted using AJAX, so not where JavaScript isn't available or is disabled.
All the normal security disclaimers apply!
I would recommend sending you the acess token in request headers when u are sending a ajax request
xhr.setRequestHeader('custom-header', 'value');
and on the server side you can fetch the request header
Couldn't you pass it back as either a hidden form element or pass it back in the query string of a ajax postback?
Example of a hook to get the post back value in global.asmx
protected void Session_Start(object src, EventArgs e)
{
if(!string.IsNullOrEmpty(Request.Form["MY_HIDDEN_FIELD_NAME"]))
{
Session["MY_SESSION_NAME"] = Request.Form["MY_HIDDEN_FIELD_NAME"]
}
}
First - why is your client generating the token (I hope I've understood you correctly there)? The server should generate the token and the client must then be responsible for maintaining it.
If it's an API token that'll only ever be used in the browser from javascript, then I recommend using an authentication cookie - all browsers know how to handle them and you can also easily expire them server-side if you no longer want to allow a particular token to have access (that's quite an important point). Also I strongly recommend against relying on server-side session to maintain the authentication session.
Authentication tokens should ideally be stateless (just like in Forms Authentication's cookie) - the burden of proof is on the client to send you a correct token, with that token containing the information you need to re-initialise the current requests state with the correct user.
If, however, it's a general purpose API for any type of client then you should allow the client to send the token to you in the query string of all requests at a very minimum. You should also support taking it in the request header as well - clients that can easily support setting request headers often prefer to because it then hides the auth token from the URL and makes formatting requests easier (there's also the potential to max out a web server's query string limit if the token is big enough).
I then recommend you look, at a minimum, at overriding MVCs AuthorizeAttribute (there are 2 - one for the 'standard' MVC 4 pipeline and one for the new Web API pipeline, & they would both need to be done if you are using both technologies. The link is for the MVC 4 one) to crack out your cookie/header/query string value. In there you can get the value, decrypt the token, identify the user and set the roles. The core code of that attribute then contains the logic for denying a request based on whether the user is authenticated/has a certain role/is a certain user.

Ajax - I need to check whether a valid session exists before every AJAX requests

I'm working on a project which uses user authentication. I'm facing a issue with my AJAX requests if there is no authenticated session present when the request is made.
I've a session timeout of 3min, so if the user keeps idle for 3 min then do some action which causes a AJAX request then the request will fail and return a 403 error. Here What I'm planning to do is intercept all the AJAX request from the page and sent a ping to the server which will return a JSON object saying whether there is a valid session. If there is one then the client will continue with the current request else it will reload the current page which will take the user to the login page and the user has to provide the credentials again.
Here is my implementation.
$("#param-ajax").ajaxSend(function(evt, request, settings) {
var pingurl = GtsJQuery.getContextPath() + '/ping.json';
var escapedurl = pingurl.replace(/\//g, "\\/");
var regexpr1 = eval('/^' + escapedurl + '\\?.*$/');
var regexpr2 = eval('/^' + escapedurl + '$/');
// Proceed with the ping only if the url is not the ping url else it will
// cause recursive calls which will never end.
if (!regexpr1.test(settings.url) && !regexpr2.test(settings.url)) {
var timeout = false;
$.ajax({
url : pingurl,
cache : false,
data : {
url : settings.url
},
async : false,
complete : function(request, status) {
if (status == "error") {
try {
// GtsJQuery.getJsonObject() converts the string
// response to a JSON object
var result = GtsJQuery
.getJsonObject(request.responseText)
if (result.timeout) {
timeout = true;
return;
}
} catch (e) {
// ignore the error. This should never occure.
}
}
}
});
// Reload the window if there is a timeout -- means there is no valid
// sesstion
if (timeout) {
window.location.reload();
}
}
});
Here everything work fine included the window.location.reload(), but the original ajax request is not aborted. Since the original AJAX request is not aborted after the page reload is triggered, the AJAX request also is sent to the server. I want some mechanism which will allow me to abort the original request if the timeout turns out to be true.
This post offers some answer, but the issue remains with the third party plugins like datatables which uses AJAX. We cannot write a error handler for those AJAX requests.
Thank you.
If I am understanding the situation, you do not need any of that. In your original ajax request, simply add an error function that will redirect the user.
errHandler = function(XMLHttpRequest, textStatus, errorThrown) {
if( textStatus.match(/forbidden/i) ) {
redirectUserToLoginHere();
}
}
$.ajax({
success: yourFunctionHere,
error: errHandler
})
Then you might be able to make some ajax wrapper which always has that errHandler so you don't have to place it in every single ajax call.
EDIT:
after some experimentation, if an 'ajaxSend' handler throws an Error, then the original request will never be sent.
Also, if the handler does
document.location = '/login';
then the original request is never sent.
Hopefully that helps :)
I changed my concept now, I'm checking for the xmlHTTPRequest in the server side using the request header 'x-requested-with'.
If it is a xmlHTTPRequest then 'x-requested-with' will have the value 'XMLHttpRequest'. Both the javascript libraries(EXTjs and jQuery) I'm using sets this header correctly.
Here is my server side code
boolean isAjaxRequest = StringUtils.endsWithIgnoreCase(request.getHeader("x-requested-with"), "XMLHttpRequest")
EDIT
If the given request is a ajax request the response will be json data which will have status 403 and it will contain a key called timeout with value true
ex: {timeout: true, ....}
Then we will handle this in the $.ajaxError() event handler will handle the error. If the error status is 403 and the timeout value is true then I'll reload the page.

Categories