Javascript: remove a cookie that was set by an ajax response - javascript

in my Webapp (that is running at localhost/myApp/), I have an ajax call like:
$.ajax({
type: "GET",
url: "http://localhost:8080/my.module/Login/login?user=abc&password=123",
xhrFields: {
withCredentials: true,
},
success: onResult,
error: onError,
});
to login on the server. The server responds with a boolean value if the login was successful and the response also contains this header:
Set-Cookie: JSESSIONID=167C57FA1E3529433938E744F7C4AC52; Path=/my.module
With the previously set xhrField "withCredentials: true", the browser automatically handles the cookie and appends the Cookie header in all following requests:
Cookie: JSESSIONID=167C57FA1E3529433938E744F7C4AC52
This works perfectly fine. But now I have the problem, that the server has a bug so it doesn't remove a session when calling the logout interface, a session can't be closed (and I don't have access to the server). In order to logout properly I would have to remove the session cookie on the client so I would get a new one from the server. But i can't find a way to access or remove the cookie, the document.cookie variable is empty in my webapp. I also tried to read the document.cookie variable from localhost/myApp/my.module/ and localhost/my.module/, but it is always empty. Another thing i tried was overwriting the cookie with
document.cookie = "JSESSIONID=ABC; Path=/my.module";
but the server requests still have the cookie from before. Can anyone tell me how i could remove it?
I know this solution would be a hack, but that's what I'm looking for, because the server programmers can't fix the bug in time and asked me to implement such a hack on the client.

"An HttpOnly cookie is not accessible via non-HTTP methods, such as calls via JavaScript " -http://en.wikipedia.org/wiki/HTTP_cookie#Secure_and_HttpOnly
First of all, the client javascript shouldn't care about the cookie.
When the server sees a login request, it creates a new cookie and calls Set-Cookie.
On receiving any other ajax request, server validates the current cookie, before serving the request.
On logout request, it will clear the cookie from its (server's) session info i.e. any further requests from the client with the same cookie will fail since its not in the server store.

Related

How to store basic auth credentials in browser cache after XHR

Context
I have a friend, his name is Bob.
Bob have a server with an application running & accessible only in local. To access this application from the outside world, Bob installed & configured a reverse proxy with nginx and the module auth_basic.
Each request go through the authentication process by the reverse proxy. Two cases :
If a HTTP GET request contain valid HTTP header parameter Authorization: Basic base64credentials, then the reverse proxy access the local application and response accordingly. Each sub-request will not require a new authentication because the browser cache the credentials and send them in every request automatically until we close the browser.
If a HTTP GET request doesn't contain valid HTTP header parameter, the reverse proxy respond directly with the HTTP header WWW-Authenticate: Basic realm="User Visible Realm". Then the browser automatically show a dialog box to enter credentials.
Everything works fine until here. It's like expected from basic auth specification.
Problem
Bob doesn't like the default dialog box from the browser and want a nice html page with a form. He configurate the nginx server to have his own html form.
The problem is that the HTML submit process for a form, by default, doesn't send the Authorization header parameter. Bob need to use a XMLHttpRequest. He implement it and receive a good 200 HTTP response from the server when the credentials are good.
Unlike the default form behavior, with the XMLHttpRequest, the browser doesn't cache the credentials automatically in case of success. So each sub-request display again the login form :'(
Bob can't change the front code of the local application to send by himself the credentials in each request (as with a SPA). Indeed, he doesn't have access to this app. He just have access to nginx conf and his own html login form. So storage is useless here.
Questions
Is it possible for bob to make the browser cache the credentials after receive the XHR response ?
(The goal is to behave like the default behavior even when he use a XMLHttpRequest)
EDIT :
Further explanation
The local app is running on localhost. Bob didn't develop this app and he can't edit it. This app doesn't provide authentication and Bob used the basic_auth module of nginx as a reverse proxy to authenticate people.
It works good but use the default behavior of browsers which implement Basic Auth specification. This behavior display an ugly form and cache the credentials when success. If Bob provide his own form the behavior go, which is normal because the Basic Auth specification require specific header parameter (Authorization: Basic ...) that HTML form can't provide. Bob need to use XHR to provide this parameter.
The question is, how get back the good behavior of the browser with XHR ?
We can only use JS on login.html and not on the local app. Here is the workflow :
HTTP GET request to the server
Server doesn't find Authorization parameter OR credentials are wrong
Server respond login.html
User provide credentials by form. XHR is emitted with Authorization parameter.
Server find Authorization parameter AND credentials are valid
Server give back the local app entry file (for example index.html)
Browser read index.html and want request other files (img, css, js...)
These sub requests will fail because no credentials provide in these requests.
If ugly default form use, the credentials are cached automatically and it works.
I precise also that a solution would be to replace nginx basic auth reverse proxy by a real backend app and another authentication system (with cookie for example which are send automatically) which would work as a reverse proxy but it is not the question asked.
EDIT 2 :
Why Bob can't use storage solution ?
In the ulgy form scenario, he doesn't have HTML login file. When the browser client ask a request to the server, the server only response a HTTP response with the WWW-Authenticate header but without HTML content. The simple fact to have this header parameter display a form. Just putting the good credentials will send back a 200 HTTP Response and the browser will cache the credentials and send it in every request with the HTTP header Authorization: Basic.
In the login.html scenario, after a success login, we need to send back in every request the HTTP header Authorization: Basic (not a cookie, because it's how work Basic Auth spec and Bob doesn't have any backend, just the nginx module). It's possible to send this header from the login.html because we can attach JS on it. But then, the next pages respond by the server will be HTML files from the local app, where Bob doesn't have access to their HTML and can't attach JS on them to provide header Authorization: Basic for the next requests. A cookie could be stored from the login.html file, but this cookie need to be retrieved from the other pages and used to send header Authorization: Basic, which is impossible because Bob doesn't have access to the JS of these pages.
Thank you in advance.
Since you're already using ajax, just have javascript set and read a cookie:
(I use jQuery here, for simplicity, replace the ajax call with the appropriate syntax if you're not using jQuery):
function getCookie(cookiename) {
/* a function to find a cookie based on its name */
var r = document.cookie.match('\\b' + cookiename + "=([^;]*)\\b");
// document.cookie returns all cookies for this url
return r ? r[1] : undefined;
// return the regex capture if it has content, otherwise return undefined
}
function getData(auth_basic) {
$.ajax({
url: 'url_of_nginx...',
headers: {
'Authorization': 'Basic ' + auth_basic
} // send auth header on xmlhttprequest GET
}).next(function ajaxSuccess(data) {
// data from the nginx
document.cookie = '_auth_cookie=' + auth_basic;
// store my auth in a cookie
}, function ajaxFailed(jqXHR) {
// do something on failure, like
showLoginForm()
});
}
function showLoginForm() {
/* function to render your form */
// attach an event handler to form submission
$('#submit_button_id').click(function form_submitted(login_evt) {
// I clicked login
login_evt.preventDefault(); // don't really submit the form
// get my field form values
username = $('#username_input_field').val();
password = $('#password_input_field').val();
// I base64 the auth string
var auth_basic = btoa(username + ':' + password);
// try to auth
getData(auth_basic);
});
}
var auth_cookie = getCookie('_auth_cookie');
if (auth_cookie === undefined) {
// I have no cookie
showLoginForm()
} else {
getData(auth_cookie)
}

JWT token with AJAX, non-AJAX, JQuery

I'm a bit frustrated with managing my JWT token during login, submits and redirects. Before I get started here's my technology stack just in case:
JQuery/Html -> Node.Js -> Java Restful Services -> MySQL.
My java Restful services manages creating the JWT Token returning it to the Node.js layer which decides what to do with it and pass it on the the client. This all works wonderfully.
To get the JWT token I'm making an ajax based authentication request to the Node middle tier, which authenticates and returns the token which is summarily crammed into localstorage on the client.
Now I have no desire what so ever to make the entire site load off a single page through ajax, it's a complex site and doing that is just dumb! I need to forward and navigate to sub pages while carrying along the JWT token.
Here's the question (finally)... How do send along the JWT token to the middle tier (node.js) without attaching it as a request or post parameter because that's a big no no? I can't seem to find a way to stuff it in the header associated with Bearer.
You need to store the token at client side using for example a cookie or localStorage
Ajax requests
Cookies: A cookie is sent automatically when making a request to the server, so you do not need to add a specific header
LocalStorage:It is needed to provide the token in each request using an HTTP header.
For example
POST /authenticatedService
Host: example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
This is an example code to show how to execute an ajax POST request using jquery
$.ajax({
type: "POST", //GET, POST, PUT
url: '/authenticatedService' //the url to call
data: yourData, //Data sent to server
contentType: contentType,
beforeSend: function (xhr) { //Include the bearer token in header
xhr.setRequestHeader("Authorization", 'Bearer '+ jwt);
}
}).done(function (response) {
//Response ok. process reuslt
}).fail(function (err) {
//Error during request
});
Form submit
With a form submission you can not control the headers set by browser, so it is not possible to set the Authorization header with a Bearer token. In this case you can
Cookie: store the JWT in a cookie that will be sent with the form data. You will need to add extra security to avoid CSRF attachs
Form param: The JWT is stored in a hidden field of the form.
Use always POST (not GET) to avoid cache of JWT
Link
A link executes a GET request. You could build the link adding the JWT as a query param url?jwt=...
But, consider in this case the security risks. Browser can cache the url and it will be present in logs. An attacker could potentially obtain them if he has access. Also the user could copy the link and use it outside your web application (e.g send it by email...)
If you use cookies, the token will be automatically sent to the server by clicking on the link, but this will only work if the user is authenticated. In this case be aware of CSRF vulnerabilities
Your only option is to store the token in a cookie if you don't want to do anything suggested above. You can't set http headers in links.

Backbone and cookies / sessions

I have configured jquery to include the cookie header with each request:
$.ajaxSetup({
cache: false,
xhrFields: {
withCredentials: true
}
});
On the server-side, I run Node with Express.
Sessions are managed with the express-sessions middleware and the Mongostore plugin.
Before adding the xhrFields configuration, I noticed that for each request, a new session was stored in the database.
I inspected the request headers and noticed that no cookie header was present.
After configuring ajax as above, I notice that still there are too many sessions stored on the server-side.
The problem is the OPTIONS request emitted by Backbone.
I checked the headers of this request and there is no session information included in these requests. Since no Session id is sent to the server, the server creates and stores a new one. It also tries to set a new cookie on the client, which will change the client's session id.
I see two options:
Configure ajax somehow to send the cookie info on ALL requests.
Disable the session middleware on the option routes (which means I will have to include the middleware on all routes that require the session and NOT include it in all other routes).
I will implement the latter. But maybe there is a better solution?

Is there any way to distinguish an HTTP request made from within a website in an actual browser from one made using code or some other tool?

I have observed a scenario where a HEAD request made from a website I'm looking at in Chrome returns a session cookie, but the identical request, with identical cookies, headers, etc. made from Java code or Postman does not.
The call is coming from this JS: $.ajax({ url: myUrl, type: "HEAD", crossDomain: true, cache: false }). Due to the latter two flags, the url looks like https://myUrl?_=1433456890.
From a browser, observing from the console, the request goes out and a session ID that looks like it comes from ASP.net is returned as a Set-Cookie response header.
When I cut and paste all cookies, headers -- including User-Agent -- and the timestamped URL from the request into some other tool, I get a login failed redirect. I've tried incrementing the timestamp in various ways to no avail.
Is there any client- or server-side way to distinguish these two cases?
Is there something about that particular ajax/HTTP call that means it can't be reproduced by an arbitrary client with the appropriate session cookies?

Set-Cookie in HTTP header is ignored with AngularJS

I'm working on an application based on AngularJS on client side and Java for my API (Tomcat + Jersey for WS) on server side.
Some path of my API are restricted, if the user doesn't have a session the response status returned is 401. On the client side, 401 http status are intercepted to redirect the user to the login page.
Once the user is authenticated, I create a session on the server side httpRequest.getSession(true);
and the response send to the client does have the Set-cookie instruction in its header :
Set-Cookie:JSESSIONID=XXXXXXXXXXXXXXXXXXXXX; Domain=localhost; Path=/api/; HttpOnly
The problem is that the cookie is never put on the client side. When I inspect cookie for localhost domain it's empty, so the next requests don't have this cookie in their header and client side still couldn't access to the restricted path of my API.
The client and the server are on the same domain but they don't have the same path and the same port number :
Client : http://localhost:8000/app/index.html
Server : http://localhost:8080/api/restricted/
Additional info : CORS is enabled on the both side :
"Access-Control-Allow-Methods", "GET, POST, OPTIONS"
"Access-Control-Allow-Origin", "*"
"Access-Control-Allow-Credentials", true
Any idea for making the Set-cookie works properly ?
Is it an AngularJS related issue ?
I found an issue in AngularJS that help me to move forward.
It seems that "Access-Control-Allow-Credentials" : true was not set on the client side.
Instruction $httpProvider.defaults.withCredentials = true was ignored.
I replace $resource call by a simple $http call with {withCredentials:true} in the config parameter.
I've managed to solve an issue very similar to yours.
My Play! backend tried to set a session Cookie which I could not catch in Angular or store via browser.
Actually the solution involved a bit of this and a bit of that.
Assuming you've solved the initial issue, which can be solved only by adding a specific domain to the Access-Control-Allow-Origin and removing the wildcard, the next steps are:
You have to remove the HTTP-Only from the Set-Cookie header, otherwise you will never be able to receive a cookie "generated" by your angular code
This setup will already work in Firefox, though not in Chrome
To make it work for Chrome too, you need to:
a) send a different domain from localhost in the cookie, using the domain your WS are "hosted". You can even use wildcards like .domain.com instead of ws.domain.com
b) then you'll need to make a call to the domain you specified in the cookie, otherwise Chrome won't store your cookie
[optional] I would remove that /api path in favor of a /
And that should to the trick.
Hope to have been of some help
In your post request on the client side, make sure to add the following:
For jquery ajax requests:
$.ajax({
url: "http://yoururlgoeshere",
type: "post",
data: "somedata",
xhrFields: {
withCredentials: true
}
});
With Angular's $http service :
$http.post("http://yoururlgoeshere", "somedata", {
withCredentials: true
});
You need work on both the server and client side.
Client
Set $http config withCredentials to true in one of the following ways:
Per request
var config = {withCredentials: true};
$http.post(url, config);
For all requests
angular.module("your_module_name").config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push(['$q',
function($q) {
return {
request: function(config) {
config.withCredentials = true;
return config;
}
};
}
]);
}
]);
Server
Set the response header Access-Control-Allow-Credentials to true.
The addition HttpOnly means that the browser should not let plugins and JavaScript see the cookie. This is a recent convention for securer browsing. Should be used for J_SESSIONID but maybe not here.
Just solved a problem like this.
I was doing this and not working...:
$cookies.put('JSESSIONID', response.data);
Cookies are saved in the browser, but when I sent a new request, all the cookies were sent exept mine. (my cookie is JSESSIONID)
then i look in the chrome inspector and i found this:
THE PROBLEM IS THAT WAS NOT THE CORRECT PATH!!!
then I tried this and my cookies were sent. yay! :
$cookies.put('JSESSIONID', response.data, {'path':'/'});
I do not know if this is your case, but this worked for me.
regards!

Categories

Resources