Showing posts with label cookies. Show all posts
Showing posts with label cookies. Show all posts

Tuesday, October 16, 2018

Django: Sessions not working as expected on Heroku

Leave a Comment

Users keep getting logged out and sessions are not persisting on my Django app on Heroku. Users can log in, but they will be randomly logged out—even on the /admin/ site.

Is there anything I'm doing wrong with my Django/Heroku config?

Currently running Django 1.11.16 on Standard Dynos.

settings.py

SECRET_KEY = os.environ.get("SECRET_KEY", "".join(random.choice(string.printable) for i in range(40)))  SESSION_COOKIE_DOMAIN = ".appname.com" CSRF_COOKIE_DOMAIN = ".appname.com"  SECURE_SSL_REDIRECT = True  # ...  MIDDLEWARE_CLASSES = [     'django.middleware.security.SecurityMiddleware',     'django.contrib.sessions.middleware.SessionMiddleware',     'django.middleware.common.CommonMiddleware',     'django.middleware.csrf.CsrfViewMiddleware',     'django.contrib.auth.middleware.AuthenticationMiddleware',     'django.contrib.auth.middleware.SessionAuthenticationMiddleware',     'django.contrib.messages.middleware.MessageMiddleware',     'django.middleware.clickjacking.XFrameOptionsMiddleware', ]   TEMPLATES = [     {         'BACKEND': 'django.template.backends.django.DjangoTemplates',         'DIRS': [os.path.join(BASE_DIR, 'templates/')],         'APP_DIRS': True,         'OPTIONS': {             'context_processors': [                 'django.template.context_processors.debug',                 'django.template.context_processors.request',                 'django.template.context_processors.csrf',                 'django.contrib.auth.context_processors.auth',                 'django.contrib.messages.context_processors.messages',             ],         },     }, ]  # ...  DATABASES = {     'default': {         'ENGINE': 'django.db.backends.postgresql_psycopg2',         'NAME': 'appname',     } }  # https://devcenter.heroku.com/articles/python-concurrency-and-database-connections db_from_env = dj_database_url.config(conn_max_age=500) DATABASES['default'].update(db_from_env) 

1 Answers

Answers 1

The problem was that SECRET_KEY was not static on Heroku. The SECRET_KEY changing was breaking sessions. The fix is to add a static SECRET_KEY to Heroku config:

heroku config:set SECRET_KEY=`openssl rand -base64 32` 
Read More

Friday, September 14, 2018

Laravel shared cookie detection issue in domain and subdomain

Leave a Comment

I am working on Laravel 5.4.30.

Imagine that we have a domain example.com and a subdomain of dev.example.com. The main domain is for master branch and the dev subdomain is for develop branch. We have cookie notice system that will be hidden after clicking on Hide Cookie Notice button. This works by setting a cookie forever. We have set the SESSION_DOMAIN configs to each domain for each environment.

For main domain:

SESSION_DOMAIN=example.com 

For dev subdomain:

SESSION_DOMAIN=dev.example.com 

Now the issue comes from here. If we go to the example.com and click on hiding the cookie notice, a cookie will be set forever for main domain. After that we go to the dev.example.com and do the same. So a cookie will be set for subdomain as well. But this cookie has been set after previous one. (The order is important) Now if we refresh the subdomain, we will see that notice again! (not hidden) The browser has read the main cookie because of .example.com set in domain parameter of cookie in the browser, so every subdomain will be affected. But the view still shows the notice because it cannot read any cookie for hiding.

Anyway I don't want to share that cookie across all subdomains. How can I achieve that? I think I should add a prefix for cookie name. But I don't know how to do it, that laravel automatically adds prefix to cookie name.

Any solutions?

3 Answers

Answers 1

You need to implement your own "retrieving" and "setting" a cookie.

Retrieving (has, get) cookies

Create yourself new class (anywhere you like, but I would do app/Foundation/Facades/) with name Cookie.

use \Illuminate\Support\Facades\Cookie as CookieStock;  class Cookie extends CookieStock {      //implement your own has(...);     public static function has($key)     {         return ! is_null(static::$app['request']->cookie(PREFIX . $key, null)); //get the prefix from .env file for your case APP_ENV     }      //implement your own get(...);     public static function get($key = null, $default = null) {...} } 

Now open up config/app.php and change corresponding alias (cookie).

Setting (make) cookies

Create yourself new provider (use artisan), and copy-paste code from Illuminate\Cookie\CookieServiceProvider.php and change namespaces. Again open up config/app.php and change corresponding service provider with the new one.

Create yourself new class (anywhere you like, but I would do app/Foundation/Cookie/) with name CookieJar.

use \Illuminate\Cookie\CookieJar as CookieJarStock;  class CookieJar extends CookieJarStock {  //Override any method you think is relevant (my guess is make(), I am not sure at the moment about queue related methods)      public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true)     {         // check before applying the PREFIX         if (!empty($name)) {             $name = PREFIX . $name; // get the PREFIX same way as before         }          return parent::make($name, $value, $minutes, $path, $domain, $secure, $httpOnly);     } } 

Update the code in your own cookie service provider to use your implementation of CookieJar (line 19).

Run $ composer dump-autoload, and you should be done.

Update

Since BorisD.Teoharov brought up, that if framework changes signature of CookieJarStocks make() (or any other cookie related function) in between the major versions, I made a example repository, that includes a test that can be used as is and it will fail if signature change happens.

It is as simple as this:

public function test_custom_cookie_jar_can_be_resolved() {     resolve(\App\Foundation\Cookie\CookieJar::class);     $this->assertTrue(true); } 

Detailed how to can be inspected in the corresponding commit diff.

Answers 2

I've setup test environments to make sure, I'm not missing any details.

As in my former answer, I thought invalidating cookies will be sufficient for that case, but as @BorisD suggested it is not, and I've confirmed that on my tests.

So there are a few important notes, coming from my experiences...

  1. Don't mix Laravel versions in subdomains - If using SESSION_DOMAIN you need to make sure your Laravel version matches (between root and subdomains), cause I've experimented with 5.4 under example.com domain and 5.6 under dev.example.com. This showed me some inconsistency in dealing with Cookies, so some important changes have been done, between these versions, and you can be sure it will not work correctly if you mix versions. I finally ended up with Laravel 5.6 on both domains, so I'm not 100% sure if that works on Laravel 5.4, but I think it should.
  2. Make sure all your subdomains use the same APP_KEY - otherwise, Laravel will be unable to decrypt the Cookie, returning null value, cause all encryption/decryption in Laravel uses this app key...
  3. SESSION_DOMAIN. In SESSION_DOMAIN I've pointed the same root domain like example.com for both domains. With this setting, I can create a cookie on root domain, and retrieve it correctly on both domains. After that setting, creating a cookie on subdomain forces root domain to receive new value from subdomains cookie also, and they are overridden. So I guess everything works here as requested in the original question.
  4. Cookie make parameters - In case you want to use a subdomain in SESSION_DOMAIN, you can safely do that also. However, you need to make sure, important let's call them global cookies are defined in a bit different way. Cookie make syntax:

    Cookie make(string $name, string $value, int $minutes, string $path = null, string $domain = null, bool $secure = false, bool $httpOnly = true)

    So what's important here, you need to put your root domain for this particular cookie on creation like this for example: return response($content)->cookie('name','value',10,null,'example.com')

Conclusions:

  1. With this config, you should be able to access your Cookies properly under subdomains and your root domain.
  2. You may probably need to update your Laravel installations to 5.6, which will force you to upgrade to PHP 7.1 at least (there were some changes to cookies in php also)
  3. And finally, in your code, don't rely on Cookie existence, but on its values only (I don't know if that's in your case).

Answers 3

You could set a prefix for the cookie name depending on the environment.

First, add COOKIE_PREFIX to your env file.

COOKIE_PREFIX=dev 

Then, use it when setting your cookie

$cookie = cookie(env('COOKIE_PREFIX', 'prod') . '_name', 'value', $minutes); 

Then, retrieve it like so

$value = $request->cookie(env('COOKIE_PREFIX', 'prod') . '_name'); 
Read More

Tuesday, September 4, 2018

Can I add a maximum expiry date to a session cookie?

Leave a Comment

I have set a session cookie, which doesn't have any expiry date and will therefore be deleted when the browser is closed.

Now I would like to add a maximum expiry date, meaning that

  • if the browser is closed before the maximum expiry date, my cookie is deleted
  • otherwise my cookie will be deleted passed the maximum expiry date

Notice that I don't want to set a "regular" expiry date because that would make my cookie persistent, failing to be deleted when the browser is closed before the expiry date.

The only solution I found is to have a second, persistent, cookie, with the maximum expiry date: I manually delete my first cookie if that second one is not found (expired). Since I would like to write as little information as possible in cookies, I'd prefer if there were another way.

After @CBroe's comment, I'm adding that the cookie is generated on the client side and I don't have an associated server side session where to store a last access timestamp

2018 update

After starting a bounty on this question, I got a couple of answers. Thank you. As a feedback, which could possibly better clarify the purpose of my question, please notice that I am not looking for code to set a persistent cookie or to implement the solution that I already have (set a second persistent cookie). I was hoping for some other creative suggestions. At the moment, I could use Zeeshan's hint and set the timestamp in the value (I would append it to the actual value). That answer is therefore so far the best candidate for being awarded the bounty.

2 Answers

Answers 1

Cookie::setMaxAge(int)

in Java we have to specify an expiration time, you can use the setMaxAge(int) method of javax.servlet.http.Cookie. It takes as a parameter the number of seconds before the cookie will expire.

For example, for a five minute expiration, we would do the following :-

// Create a new cookie for userID from a fictitious // method called getUserID Cookie cookie = new Cookie ("userID", getUserID());  // Expire the cookie in five minutes (5 * 60) cookie.setMaxAge( 300 ); 

When the cookie is sent back to the browser, using HttpServletResponse.addCookie(Cookie), it will only be returned by the browser until the expiration date occurs. If you'd prefer, you can also specify a negative value for setMaxAge(int), and the cookie will expire as soon as the browser exits. Note however that not everyone will shutdown their browser, and it might be available for minutes, hours even days. Finally, specifying a value of zero will expire the cookie instantly.

Here is full Java Servlet example.

// Import required java libraries import java.io.*; import javax.servlet.*; import javax.servlet.http.*;  // Extend HttpServlet class public class HelloForm extends HttpServlet {     public void doGet(HttpServletRequest request, HttpServletResponse response)       throws ServletException, IOException {        // Create cookies for first and last names.             Cookie firstName = new Cookie("first_name", request.getParameter("first_name"));       Cookie lastName = new Cookie("last_name", request.getParameter("last_name"));        // Set expiry date after 24 Hrs for both the cookies.       firstName.setMaxAge(60*60*24);       lastName.setMaxAge(60*60*24);        // Add both the cookies in the response header.       response.addCookie( firstName );       response.addCookie( lastName );        // Set response content type       response.setContentType("text/html");        PrintWriter out = response.getWriter();       String title = "Setting Cookies Example";       String docType =          "<!doctype html public \"-//w3c//dtd html 4.0 " + "transitional//en\">\n";        out.println(docType +          "<html>\n" +             "<head>                <title>" + title + "</title>             </head>\n" +              "<body bgcolor = \"#f0f0f0\">\n" +                "<h1 align = \"center\">" + title + "</h1>\n" +                "<ul>\n" +                   "  <li><b>First Name</b>: "                   + request.getParameter("first_name") + "\n" +                   "  <li><b>Last Name</b>: "                   + request.getParameter("last_name") + "\n" +                "</ul>\n" +             "</body>          </html>"       );    } } 

And HtML file will be

<html>    <body>       <form action = "HelloForm" method = "GET">          First Name: <input type = "text" name = "first_name">          <br />          Last Name: <input type = "text" name = "last_name" />          <input type = "submit" value = "Submit" />       </form>    </body> </html> 

Answers 2

if you want to keep cookie as session cookie you can not set expiry. so you can either set timestamp as cookie value or create new cookie and set value as timestamp.

var timestamp = (new Date()).getTime() document.cookie = "cookiename=value;  path=/"; document.cookie = "expirycookie="+timestamp+";  path=/"; 

for only client side solution you can set interval to check cookie timestamp value. add below code to all your pages

   var interval = setInterval(function(){          var timeStamp = getCookie('expirycookie')           if(!timeStamp){clearInterval(interval); return}          var cookieDuration = 5*60*1000 //expire cookie after 5 min          if(timeStamp < (new Date()).getTime() - cookieDuration){            //cookie expired delete here            document.cookie = 'cookiename=value; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';            document.cookie = 'expirycookie=value; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';           clearInterval(interval)         }       },1000)  function getCookie(cname) {   var name = cname + "=";   var ca = document.cookie.split(';');   for (var i = 0; i < ca.length; i++) {     var c = ca[i];     while (c.charAt(0) == ' ') c = c.substring(1);     if (c.indexOf(name) != -1) return c.substring(name.length, c.length);   }   return ""; } 
Read More

Monday, August 20, 2018

Undestanding how secure/httponly cookie works for java applications

Leave a Comment

I have been having a behaviour that I can only qualify as weird due to my current level of understanding of this.

I have apache version : 2.4.7 on Ubuntu proxying through AJP 1.3 tomcat 7.0.52.0 running a spring application (MVC) with apache shiro 1.2 as security framework.

I have set headers entry in apache2.conf as shown below

Header always append X-Frame-Options SAMEORIGIN Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure 

I have the very same behaviour if flags is enforced on tomcat side using either or all of the methods below:

  • conf/context.xml with useHttpOnly="true" attribute of the context tag
  • conf/server.xml with secure="true" attribute of the ajp or http
  • connector WEB-INF/web.xml with the following

    <cookie-config> <http-only>true</http-only> <secure>true</secure> </cookie-config>

After this what happens is that at at /login there is a secure and httponly flag, after the authentication is successful all these flags vanish within the app, throughout any call to the server. Once the user logs out, the flags come back with an extra one : DeleteMe on both the jsessionid and RememberMe.

This /login page creates the jsessionid with secure and httponly flags jsessionid at login

When the authentication is successful the 2 step auth jsessionid has no flags

2 step authentication

In the account dashboard too there is no flag

account Dashboard

But at the logout the flags are back logout

My questions though are

1: is this the usual behaviour
2: If this is the actual behaviour, does this mean the cookie is secure throughout the life of the session id?

1 Answers

Answers 1

This is not usual behavior/observation, as those flags are not applicable to the Cookie request header as sent to a server to maintain state. The values received are used by, but not transmitted by, the client. You're being mislead by that diagnostic interface showing the columns for the Cookie header. Set-Cookie and Cookie are not symmetrical this way.

The only way to tell if your non-browser client honors the "SECURE" setting is to coax it into sending a non-HTTPS request to the same domain/path specified in the cookie and observing if it omits the cookie previously set as SECURE.

Read More

Wednesday, July 18, 2018

Firefox does not keep cookies sent by cross-domain even with all CORS allow

Leave a Comment

I experience a problem with Firefox while Chrome works fine. Here is the situation:

  • Website1.com returns an html page in SSL.
  • This page makes a request to Website2.com in SSL either via img tag or XMLHttpRequest (same issue).
  • Website2.com returns a cookie to be set for itself
  • Firefox ignores this cookie. It is never stored even though it shows in the console.
  • The console doesn't complain about anything.

Client sends:

Origin: https://website1.com 

Server returns:

Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: * Access-Control-Allow-Methods: * Access-Control-Allow-Origin: https://website1.com Access-Control-Expose-Headers: * Set-Cookie: ... 

What else am I missing about CORS?

Thanks!

1 Answers

Answers 1

Access-Control-Allow-Credentials: true 

Is a special flag. If one side declares it other also have to declare it or else it's security failure and browser will not accept data.

So add the same header to client request. (Or if you control server, consider doing without cookies and passing data with other mechanism)

Read More

Tuesday, June 5, 2018

Issue with storing and recalling cookies Javascript

Leave a Comment

So, I'm trying to save and load a cookie that contains a list of product details and then display these product details to the page.

However when I run this what I receive is a

ReferenceError: Can't find variable: GetProductPrice

when trying to read the cookies I stored using the GetProductPrice() function.

The cookie storer runs when the website loads for the first time. It creates a list of products which I later intend to use as part of a shopping cart. It then should save this cookie so that it can be accessed again if the user goes to a different page. This way if the user adds an item to the cart, the carts variables won't be later reset. However as I said before, this doesn't work. The load and save cookie functions are created by someone else on Stack Overflow and work fine.

Here is the code for the cookie saver that should run only the first time the user accesses the site:

function setUpProducts() { //in here we define each of the products when we first start up. This way we know what products the customer has access to.   setUpPages();   try {     hasRun = getCookie('hasRunCookie'); //get the has run cookie from before, or at least try to   } catch {     hasRun = 0;   }   //if (hasRun != 1){ //only run this the first time, otherwise we dont need to.   if (hasRun != 1) {     hasRun = 1;     createCookie('hasRunCookie', hasRun);     var dataList = []; //array for holding the raw data     var nameList = [];     var priceList = [];     var amountList = []; //lists for temporrily holding data before it is put in the product list's product objects.     var set1 = 0; //allows differentiating between different values in the raw data     var productIds = 0; //product ID to differentiate between products easily      $.get('../productdata.txt', function(data) { //the text file product data contains all the product infomation so that it can be changed at a moments notice.       //var fileDom = $(data);        dataList = data.split("\n");       $.each(dataList, function(n, elem) {         $('#myid').append('<div>' + elem + '</div>');       });     });      var i;     for (i = 0; i < dataList.length; i++) { //this gets the infomation from the text file and loads it into the products       if (set1 == 0) {         nameList.push(dataList[i]);         set1 = 1;       } else if (set1 == 1) {         priceList.push(dataList[i]);         set1 = 0;       }     }     while (productIds != 8) { //make the products, programing counting always starts at 0, so 8 is actually the 9th number.       var productObject = {         productID: productIds,         productName: nameList[productIds],         productPrice: priceList[productIds],         purchasedAmount: 0       };       productList.push(productObject); //put each product into the product list.       productIds += 1;     }     var json_str = JSON.stringify(productList); //bottle and store our list of products     createCookie('ProductListCookie', json_str);     //}    } 

Here is the code used to load the cookie and display a product's price on the relevant product page:

function GetProductPrice(productIdz) {   var json_str = getCookie('hasRunCookie');   productList = JSON.parse(json_str);   for (i = 0; i < productList.length; i++) {     if (productList[i].productID == productIdz) {       productHolder = productList[i];       document.getElementById("price").innerHTML = productHolder.productPrice;     }   } } 

6 Answers

Answers 1

Would have made a comment, but can not.

First, if you get: ReferenceError: Can't find variable: GetProductPrice, it might be that your GetProductPrice function is not defined in that page, or maybe not yet, check the loading order of your scripts.

Second:

function GetProductPrice(productIdz){   var json_str = getCookie('hasRunCookie'); // this is 1   productList = JSON.parse(json_str) // this also is 1 

maybe

getCookie('ProductListCookie') 

would work?

Answers 2

Use localStorage

//Set localStorage.setItem('ProductListCookie', json_str);  //Get var json_str = localStorage.getItem('ProductListCookie'); 

Answers 3

When you will debug your code in browser I believe that you will find problem yourself. Chrome or FF are best to do it. See Chrome DevTools or Firefox Developer Tools. When you will put breakpoint at first line of your code, than you can see whats going there.

Answers 4

As others have mentioned ReferenceError: Can't find variable: GetProductPrice means that JS simply can't find your function. Make sure the code that calls GetProductPrice() can access the function.

On a separate note – you shouldn't use cookies for this. Cookies are sent to the server with each request and are going to increase the load on your server and slow down the page load for users.

For your purposes consider using localStorage, it is supported by all modern browsers and even IE 8+, and does everything you need without the overhead of sending unnecessary info to the server. And you will barely need to change your code.

Answers 5

I think you are defining the function when page.onload occures. If Yes you have to define your function like this:

window.GetProductPrice = function(productIdz){ 

check this and let me know the result.

Answers 6

Generally speaking, cookies are limited to 4k, so localStorage is better.

Read More

Sunday, March 4, 2018

R httr post-authentication download works in interactive mode but fails in function

Leave a Comment

the code below works fine in interactive mode but fails when used in a function. it's pretty simply two authentications POST commands followed by the data download. my goal is to get this working inside a function, not just in interactive mode.

this question is sort of a sequel to this question.. icpsr recently updated their website. the minimal reproducible example below requires a free account, available at

https://www.icpsr.umich.edu/rpxlogin?path=ICPSR&request_uri=https%3a%2f%2fwww.icpsr.umich.edu%2ficpsrweb%2findex.jsp

i tried adding Sys.sleep(1) and various httr::GET/httr::POST calls but nothing worked.

my_download <-     function( your_email , your_password ){          values <-             list(                 agree = "yes",                 path = "ICPSR" ,                 study = "21600" ,                 ds = "" ,                 bundle = "rdata",                 dups = "yes",                 email=your_email,                 password=your_password             )           httr::POST("https://www.icpsr.umich.edu/cgi-bin/terms", body = values)         httr::POST("https://www.icpsr.umich.edu/rpxlogin", body = values)          tf <- tempfile()         httr::GET(              "https://www.icpsr.umich.edu/cgi-bin/bob/zipcart2" ,              query = values ,              httr::write_disk( tf , overwrite = TRUE ) ,              httr::progress()         )      }  # fails  my_download( "email@address.com" , "some_password" )  # stepping through works debug( my_download ) my_download( "email@address.com" , "some_password" ) 

EDIT the failure simply downloads this page as if not logged in (and not the dataset), so it's losing the authentication for some reason. if you are logged in to icpsr, use private browsing to see the page--

https://www.icpsr.umich.edu/cgi-bin/bob/zipcart2?study=21600&ds=1&bundle=rdata&path=ICPSR

thanks!

0 Answers

Read More

Tuesday, December 19, 2017

Cookies are being deleted

Leave a Comment

I have an asp.net mvc application and I have this strange thing that is happening and I don't know why.

Context:

I have a page that allows the user draw and edit images. This page has a feature that each 5 minutes, she made a ajax call to the server using jquery, saving the current state of the project on the database and another call to save an image of the project that is storage in a proper place.

Problem:

With the browser minimized, when this feature is launched, after the ajax call to the server, the cookie Customer is deleted. But when the chrome is maximized, this works fine.

Notes:

  1. This only happens when the browser is minimized
  2. This is happening, at least, in chrome and firefox.
  3. This is only happens on the production environment. On my local machine and on visual studio i cannot reproduce the issue
  4. The asp.net session cookie is mantainned

I know that is difficult for you to help only with this information, but, if you can give me clues it will be really helpful. I'm trying to expose the problem so we can match similar issues to find the best solution for this case.

Thank you in advance

[EDIT]:

I have some new about the issue:

  1. Using Chrome Version 63.0.3239.84 (Official Build) (64-bit);
  2. Firefox quantum 57.0 (64-bit);
  3. Contrary that I first thought, this is happens even if the browser is not minimized and precisely 3 mints after the page load (if I call the function)
  4. The cookie are not being deleted but the content of the cookie is: enter image description here
  5. This is a asp.net web application
  6. The console does not gives any errors
  7. The version of query is 2.1.3
  8. Follows the jquery call code:

    makeAjaxCall(ajaxData) { var localData = ajaxData.data ? ajaxData.data : {},     urlVariables = {};  localData.cmd = ajaxData.cmd;  var controlerURL = ajaxData.uploadUrl ? HelperJSViewBag.getValue("ajaxCAllUploadURL") : ajaxData.controller;  if (typeof ajaxData.data.urlVariables == "undefined")     ajaxData.data.urlVariables = [];  let editorVersion = ""; let forceEditorVersion = "";  if (typeof UrlParameters != "undefined") {     editorVersion = UrlParameters.getInstance().editorVersion;     forceEditorVersion = UrlParameters.getInstance().forceEditorVersion; } else if (typeof HLinks != "undefined") {     editorVersion = HLinks.getUrlVariable("editorVersion");     forceEditorVersion = HLinks.getUrlVariable("forceEditorVersion"); }  if (editorVersion.length > 0)     ajaxData.data.urlVariables.push({         name: "editorVersion",         value: editorVersion,     });  if (forceEditorVersion.length > 0)     ajaxData.data.urlVariables.push({         name: "forceEditorVersion",         value: forceEditorVersion,     });  if (typeof ajaxData.data.urlVariables != "undefined" && ajaxData.data.urlVariables.length > 0)     for (var i = 0; i < ajaxData.data.urlVariables.length; i++)         urlVariables[ajaxData.data.urlVariables[i].name] = ajaxData.data.urlVariables[i].value;  localData = this.fillLocalData(localData);  return $.ajax({     type: 'POST',     data: localData,     url: controlerURL + "?" + $.param(urlVariables),     success: function (data) {         try {             var result = JSON.parse(data),                 status = result.status;              delete result.status             switch (status) {                 case 1: ajaxData.sucess && ajaxData.sucess(result.data); break;                 case 2: ajaxData.insucess && ajaxData.insucess(ajaxData.errorHandler && ajaxData.errorHandler.handle && ajaxData.errorHandler.handle(result)); break;             }         }         catch (ex) {             ajaxData.insucess && ajaxData.insucess(ajaxData.errorHandler && ajaxData.errorHandler.handle && ajaxData.errorHandler.handle(ex));         }     },     error: function (data) {         ajaxData.insucess && ajaxData.insucess(ajaxData.errorHandler && ajaxData.errorHandler.handle && ajaxData.errorHandler.handle(data));     } }); } 

2 Answers

Answers 1

I would like to see your code on the ASP.NET side, but without that, I can only guess: perhaps check to see if you are setting a cookie expiration date? Here is an example of a cookie that is created with 1 day expiration:

Dim myCookie As HttpCookie = New HttpCookie("Customer") myCookie.Expires = Now.AddDays(1) Response.Cookies.Add(myCookie) 

If you omit myCookie.Expires property, the cookie "will expire when the user session expires", ref. https://msdn.microsoft.com/en-us/library/78c837bd.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1

Answers 2

  1. you can increase the expiration time of your Cookies (Within the code where you are creating cookies).

  2. You can use localStorage in your code instead of Cookies to hold the value.

For examlpe localStorage.setItem to set value and localStorage.getItem to get the value from local storage.

Read More

Monday, December 18, 2017

.net Core MVC: X-SRF-TOKEN not accepted, 400 returned

Leave a Comment

I have a .net core app using angularJS, and I want to protect the api calls protected by our cookie based authentication. I Followed the steps in this article:

https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery

  • I added the services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); to my services configuration
  • I am seeing the XSRF-TOKEN cookie in my developer tools when loading the page.
  • I am seeing the X-XSRF-TOKEN header being added to my $http sent requests.

  • I have added the [AutoValidateAntiforgeryToken] to my controller that is handling the ajax request.

  • I have ssl enabled, and am accessing the pages via https.

I can make GET requests fine through this api endpoint as expected. However, I am receiving a 400 error without any details of why the request was bad on PUTs and POSTs.

I know the X-XSRF-TOKEN is on the request, (seen in the network tab of chrome dev tools) so I am unsure what I am missing to allow these requests to be received correctly.

TL;DR: Why is .net core rejecting my valid AntiforgeryToken?

UPDATE Attempted @joey's suggested solution, but it did not work, still receiving 400 responses. code below reflects another solution I tried to fix this problem (aka angularjs's solution to setting default cookies and headers for cross site scripting protection)

I have also attempted to configure AngularJS to change what the cookie and header names match what I configured in my Startup.cs. I changed their names to try both XSRF-TOKEN (cookie) and X-XSRF-TOKEN (header) as well as CSRF-TOKEN and X-CSRF-TOKEN, and while the configurations within angular is correctly using the new default to what ever I provide, my authentication code in .net core is still not working.

for more information here is how i am configuring AngularJS:

app.config(function ($httpProvider) {     $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';     $httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN'; }); 

here is the ConfigureServices line I have in my Startup.cs file:

services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN"); 

and lastly here is the code I added to the Configure method of the Startup.cs file:

    app.Use(next => context =>      {         string path = context.Request.Path.Value;         if (path.Contains("/MyProtectedPath/"))         {             var tokens = antiforgery.GetAndStoreTokens(context);             context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken,                 new CookieOptions { HttpOnly = false });         }         return next(context);     }); 

UPDATE 2: I have added the following to my controller action:

if (HttpContext.Request.Method.ToLower(CultureInfo.InvariantCulture) != "get") {     await _antiforgery.ValidateRequestAsync(HttpContext); } 

AntiforgeryValidationException: The provided antiforgery token was meant for a different claims-based user than the current user.

I thought maybe antiforgery.GetAndStoreTokens(context) might be overriding the current cookie sent on the page load, so I make it only hit that code on GET requests, but I get the same result for the posts.

2 Answers

Answers 1

In the code you have posted, you are adding the header X-XSRF-TOKEN, but the header should be X-CSRF-TOKEN.

The example from the web page you linked provides the example:

services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");  

Update:

Thanks for clarifying with the additional code & information. The means of implementing CSRF selected here is one which passes the token as a header on the response of the initial HTML file. Here is an example of how such a case might be configured for a SPA web application:

app.Use(next => context => {     string path = context.Request.Path.Value;     if (         string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||          string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase)     )     {         var tokens = antiforgery.GetAndStoreTokens(context);         context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken,              new CookieOptions() { HttpOnly = false });     }      return next(context); }); 

However, with your service configured to using the String.Contains method [1], antiforgery.GetAndStoreTokens(context) is invoked for any path containing /MyProtectedPath/ anywhere. This means it has been configured in such a way that matches not only /MyProtectedPath/, but also /MyProtectectedPath/a/b/c or /a/b/c/MyProtectedPath/.

To check the CSRF token sent in subsequent requests of an applicable HTTP method as shown below:

if (string.Equals("POST", context.Request.Method, StringComparison.OrdinalIgnoreCase)) {     await antiforgery.ValidateRequestAsync(context);     // The line above will throw if the CSRF token is invalid. } 

If the method GetAndStoreTokens is called before this for any matching path, the token will be overwritten before it is checked, which is why .net examples will typically order GetAndStoreTokens first, but with the specific condition for looking at the path and HTTP method.

[1] https://msdn.microsoft.com/en-us/library/dy85x1sa(v=vs.110).aspx

Answers 2

Joey's answer seems to be right. But since you've mentioned it doesn't work for you here's what I had to do to get it working.

First, from the docs:

AngularJS uses a convention to address CSRF. If the server sends a cookie with the name XSRF-TOKEN, the Angular $http service will add the value from this cookie to a header when it sends a request to this server. This process is automatic; you don't need to set the header explicitly. The header name is X-XSRF-TOKEN. The server should detect this header and validate its contents.

Add the AntiForgery service

Add the Antiforgery service to the service collection in Startup.ConfigureServices() after the call to AddMvc():

services.AddAntiforgery(options => {     options.HeaderName = "X-XSRF-TOKEN"; }); 

We're basically telling ASP.NET to look for the X-XSRF-TOKEN header while validating the xsrf token.

Send a cookie with the token

Now on every request from the SPA, we need to send a XSRF-TOKEN cookie with the token value. We can do that with a quick middleware:

// TODO: Refactor this to a separate middleware class app.Use(next => context => {     // TODO: Add if conditions to ensure the cookies     // are only sent to our trusted domains      // Send the token as a javascript readable token     var tokens = antiforgery.GetAndStoreTokens(context);     context.Response.Cookies.Append(         "XSRF-TOKEN",          tokens.RequestToken,          new CookieOptions() { HttpOnly = false }     );      return next(context); }); 

Validate your actions

The easiest way to validate the tokens is to add the [ValidateAntiForgeryToken] attribute. A better option is to configure MVC to apply the AutoValidateAntiforgeryToken globally for all actions with the following in Startup.ConfigureServices():

services.AddMvc(options =>      options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute())); 

Read the docs on token validation.

Read More

Wednesday, November 29, 2017

How to prevent HTTP session flood attack

Leave a Comment

Flood Attack: In short, a hacker can keep hitting the server (without cookie) to force Java container to keep creating new session.

I am using Spring Security to manage session. I realize jsessionid keep being created before login, this is not what I want.

So I did:

1) in Spring security config:

sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER) 

2) disable session creation in jsp. Because I am using apache tile, due to it is using dynamic include, so I have to disable session creation in all the jsp fragment. This is very tedious.

<%@page session="false"%> 

First glance, it is fine, but there is a scenario I still got the session created.

Let's say before login, I visit a url that only can be visited after authenticated, Spring will redirect me to login page.

Before I am redirected, the response already instruct to set a new cookie, a session already created.

My Question:

1) Is session flood attack a serious issue? Should I really take care of it?

2) Is there any better way to handle this issue? Any best practise?

3) What happen to my code? It should work actually, I suspect the cookie is created by Spring, although I already set it to SessionCreationPolicy.NEVER. I can't set it to Stateless, I still need the session after login.

I am more concerned session attack compare to DDOS actually, I have also set .maximumSessions(1) in Spring to prevent multiple login. But above issue happen before login. Please help. Thanks.

1 Answers

Answers 1

Your point looks valid, it probably can be a serious issue if not handled. I found there is already an open issue on this topic. But there is a work around available to control this behavior.

public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {         public HttpSessionRequestCache getHttpSessionRequestCache() {             HttpSessionRequestCache httpSessionRequestCache = new HttpSessionRequestCache();             httpSessionRequestCache.setCreateSessionAllowed(false);             return httpSessionRequestCache;         }          @Override         protected void configure(final HttpSecurity http) throws Exception {             http.requestCache().requestCache(getHttpSessionRequestCache()); } 

Refer following links for more details.

https://github.com/spring-projects/spring-security/issues/4242

How to stop Spring Security from creating a new session?

Read More

Wednesday, November 8, 2017

Android - Extrac SameSite=Strict cookie from Webview or URL

Leave a Comment

I'm using the following code within the onPageFinished() of a WebViewClient to read the cookies . But some cookies are set "SameSite=Strict", which I cannot get using the method below:

 @Override  public void onPageFinished(WebView view, String url){      String cookies = CookieManager.getInstance().getCookie(url);      Log.d(TAG, "All the cookies in a string:" + cookies);  } 

Can anybody tell me how to get the cookies which are set "SameSite=Strict" ?

1 Answers

Answers 1

It is officially a bug in Chromium and needs to be (hopefully will be) fixed there.

Read More

Sunday, November 5, 2017

Cookies visible from HttpResponseMessage but not in Javascript

Leave a Comment

I have a login service which passes user login information to my application when I login. This will call first page of my application on successful login. The user info is passed as part of a cookie in this request.

I am trying to read these cookies from the request in my Web API request using the below code.

 CookieHeaderValue getAccessUserInfo = request.Headers.GetCookies("GAUSERINFO").FirstOrDefault();                  if (getAccessUserInfo != null)                 {                     userInfo = getAccessUserInfo["GAUSERINFO"].Values.Get("corporate_id");                     Logger.Info(string.Format("User Cookie {0}", userInfo));                     return userInfo;                 } 

But if I am trying to read the same cookie from javascript or angular js, I am not able to see that cookie in the collection. Below is the code I am using to read the cookie in angular js.

console.log($document[0].cookie); 

This is the cookie I can see from the result. The cookie I am expecting is GAUSERINFO along with the below cookies.

enter image description here

Is there a way to read these cookies from angular js or atleast pass that request body to the API so that I can read the cookie in my API using C# code.

1 Answers

Answers 1

JavaScript doesn't have access to HTTP only cookies for security reasons. In order to prevent XSS attacks, generally user session/login information should be HTTP only. If an attacker were to get the user's session information, they could impersonate the user. If you want to expose user info like corporate_id to the client side, it can be in a separate cookie that is not marked as HttpOnly.

From MSDN for HttpCookie.HttpOnly:

Stolen cookies can contain sensitive information identifying the user to the site, such as the ASP.NET session ID or forms authentication ticket, and can be replayed by the attacker in order to masquerade as the user or obtain sensitive information. When an HttpOnly cookie is received by a compliant browser, it is inaccessible to client-side script.

To use corporate_id client side, create a separate cookie with the only the information you need client side and when you create it in your C# code be sure to set HttpOnly to false.

Edit: If your use case is to forward the cookie along to your API using Angular's $http service, there's additional configuration you can do for it which is detailed in this answer: https://stackoverflow.com/a/17065576/6950124

The summary of it being that you need to set $httpProvider.defaults.withCredentials = true; and potentially also configure your web server to send back the header Access-Control-Allow-Credentials: true when the browser initially requests the page. This lets the browser know that your JavaScript code can have partial access to the HttpOnly cookies.

The underlying browser code for making the AJAX call should forward HttpOnly cookies if you have everything configured properly.

Read More

Friday, November 3, 2017

Express Session Different Expiration

Leave a Comment

I have the following code to create sessions in Express.

app.use(session({     secret: 'randomstring',     saveUninitialized: false,     resave: false,     cookie: {         secure: true,         maxAge: 60000 * 60 /* 1 hour */     } })); 

I mainly use this to store session data in Passport.js. Currently after 1 hour users get automatically logged out. Or the session ends and users get logged out.

Is there an easy way to store more data in the session and have different expiration dates for different information? So say I want the Passport.js (req.session.passport.user) session data to have an expiration of 1 hour. And I want another piece of data say for example (req.session.myDataA) to have an expiration of 30 days. And I want another piece of data (req.session.myDataB) to have no expiration and stay active forever.

Is there an easy way to achieve this behavior in Express Sessions?

2 Answers

Answers 1

You can use cookies and set different expirations on different cookie names. So you can have multiple cookies that would hold data and each one would have a specific expiration.

That being said, I would say that both sessions and cookies wouldn't be the correct solution to your problem. I would keep your sessions lean and store the data in a database. Considering you're using sessions, you could use Redis and store data with expirations. You could assign the key in Redis to your session. For example:

req.session.myDataA = 'user_id:myDataA' req.session.myDataB = 'user_id:myDataB' 

When you set your data in Redis, you can use the key user_id:myDataA and set it to expire.

// Will expire in 1 hour SET user_id:myDataA "your data" EXPIRE user_id:myDataA 3600 

While the key will still be in session, you can check if the value is null or has the data you're expecting.

I know this perhaps sounds a little more complicated, but even as a good practice with sessions, you really don't want to be storing a lot of data, beyond keys of reference as it becomes very difficult to manage.

If you're using MongoDB, you could also set documents to expire. However, if you're not using either, Redis would generally be the easiest to setup and acts a good session store.

Edit:

As commented below, instead of expiring the sessions which you can't at different time frames, is to just set a field in your data with expiration time. Then when you get that data, check if it's passed expiration (i.e., 1hr, 30days, etc.).

Answers 2

You could set the maxAge of session in another middleware

code:

// after session and passport middleware app.use(function(req, res, next) {     if (req.session.user) { // some condition         req.session.cookie.maxAge = 1000 * 60 * 60 * 24 // 24 hours     } else if (req.session.myData) {         req.session.cookie.maxAge = 1000 * 60 * 60      }     // another condition      // save the session     req.session.save()      // add next at the end of middleware      // so it can pas data edited to another middleware     next() }) 
Read More

Thursday, October 26, 2017

Cookie session driver won't save any validation error or flash data

Leave a Comment

I am having a hard time with the cookie session driver in Laravel.

I have a simple form with a validation in place. This is my method for saving this form data:

public function store() {     $this->validate(request(), [         'name'        => 'required',         'title'       => 'required',         'description' => 'required|max:600',         'image'       => 'required|file|mimes:jpeg,png',     ]);      $member = TeamMember::create(request()->all());     $member->addImage(request()->file('image'));      return redirect()->route('backoffice.team-members'); } 

Pretty simple.

The problem is that, when using the cookie session driver, if I save this form with a description that's 1024 characters long, I'll get redirected back but with no flash data and no $errors in the view for the next request to handle.

Example:

enter image description here

This is a POST after using this line:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce gravida eros ut leo commodo luctus. Nulla neque dui, laoreet quis felis in, porta tincidunt felis. Phasellus in lacus et sem condimentum ornare. Praesent vitae nisi tempus, gravida tortor eu, convallis dui. Cras lacinia posuere scelerisque. Vestibulum tincidunt purus id sollicitudin varius. Sed eros urna, mattis nec nunc eu, finibus suscipit ipsum. Aliquam varius faucibus congue. Vivamus convallis imperdiet sem a commodo. Proin cursus feugiat sem a pharetra. Curabitur rhoncus non quam sit amet lacinia. Sed ut nisl id odio faucibus vehicula vel ut erat. Vestibulum ut iaculis magna. Quisque sit amet massa sodales, suscipit nisl eu, dapibus elit. Morbi posuere ligula pretium commodo semper. Nam odio elit, rutrum finibus tortor eget, viverra viverra metus. Proin tincidunt tempor ex pretium rhoncus. Proin egestas erat sed eros congue, mollis gravida magna bibendum. Pellentesque vel bibendum nunc. Orci varius natoque penatibus et magnis dis viverra fusce. 

In the description field. 1024 bytes to be exact.

Instead, if I just fill the field with some more dummy data but nothing too crazy:

enter image description here

If I change the session driver to file:

enter image description here

... it works.

But this does not fix my problem. I do need to use the cookie driver for session as the production website is running in 3 different datacenters to achieve high availability. Using the cookie for the session allows the user to hit any of the 3 servers and still continue with it's request without having to use any sticky session or any central session driver.

Using the database as a driver—which is also in a cluster with HA—is not an option as this is a really high traffic website and that would be a write per request which doesn't sound appealing at all. I would like to prevent that at any cost.

There is anyway this can be solved?

I must say this is the backoffice of the website, but soon the user in the frontend will be able to also write more than 1024 characters in a textarea... so if I just change the driver for the backoffice doesn't help, as we will run into the same for our users.

2 Answers

Answers 1

The cookie session driver is not suitable for applications that must store any significant amount of data in the user's session. Browsers typically limit data stored in one cookie to around 4 KB (4096 bytes). As we found, we can easily exhaust this capacity by attempting to store a 1024-character-long string in the session cookie—the "Lorem ipsum..." string in the question contains only ASCII characters, and each ASCII character is represented using 4 bytes, so 1024 × 4 = 4096 bytes.

As we can see, we quickly begin to run out of space when we need to store additional items in one session cookie, such as the serialization metadata for PHP values, or when the data contains UTF-8 characters that consume more than 4 bytes per character. To continue to use cookies to store session data greater than 4 KB, we'd need to write a custom session driver that partitions session data across multiple cookies for each response and then reassembles them on each request. For good reason, no existing packages do this that I'm aware of. This brings me to my next point...

I want to strongly discourage the use of cookies for storing a full session (instead of just a session ID). This approach can expose security vulnerabilities, adds overhead to requests and responses (including requests for static assets on the same domain), risks desynchronizing data (right-click app tab → Duplicate to see what happers), complicates tests and debugging, and causes problems when storing certain kinds of data.

For the distributed application in the question, we have four options:

  1. Store the session data on a central server accessed by each application instance
  2. Store the session state at each site, and replicate it between sites
  3. Use "sticky" sessions to anchor a user to one site after starting a session
  4. Write a non-trivial amount of code to implement a custom session driver that allows the client to pass session state without using cookies, or one that partitions cookies

I think we can reasonably eliminate the fourth option. Unless we have a very good reason to avoid using the first three approaches, and for typical applications we usually don't, we cannot justify the amount of work, complexity, and overhead needed to build a system to transfer session data back and forth across HTTP when the first three options are standard, widely-accepted solutions.

Based on the information in the question, it sounds to me like you already understand the concepts and implications behind the other three options, so I won't elaborate on explanations for each one (but do comment to let me know if I should).

For applications without advanced infrastructure requirements, I recommend the third approach: sticky sessions. These are relatively easy to set up and usually require configuration on the load balancer so that once a client starts a session with an application server, the load balancer routes any subsequent requests to the same server until the session ends. For high-availability, we can combine this approach with the Redis session driver and Redis servers configured for master-slave replication between the datacenters, and, optionally, Redis Sentinel to automate failover. Redis suits session data well and offers better performance than a relational database. If we have multiple application instances at each data center, Redis provides a central location for session data for all instances at one site.

For completeness, and to directly answer the question, here's an overview for the development needed to create a cookie-based session driver that handles session data greater than 4 KB. Again, I recommend one of the other approaches described above:

  • First, we'll need to create a new class that implements PHP's SessionHandlerInterface (check out Laravel's cookie session handler for a starting point—we can probably extend this class).

  • The write() method of this class will need to serialize the session $data and then split the data into chunks smaller than 4 KB (for some browsers, less that 4093 bytes). Be sure to account for multibyte characters. Before splitting the data, we may also want to encrypt it if the session contains sensitive information or if we don't want clever users to mess with the values. Then, the method should add a new cookie for each chunk of the session data. Each cookie will need to contain the sequence in its name, and we can add an additional cookie that contains the number of chunks.

  • The read() method will perform these operations in reverse. First, it will reassemble each chunk from the cookies in the request using the value of the cookie that contains the number of chunks, and then, optionally, decrypt the data if we encrypted it.

  • The destroy() method should clear the contents of each cookie that contains a chunk.

  • Then we'll choose a name for the session driver, such as cookie-extended, and, in a service provider's boot() method, register it as an available driver using :

    Session::extend('cookie-extended', ...); 

    We'll need to instruct the application, in config/session.php or in .env, to use the new driver for user sessions.

If you do decide to go down this path, be sure to test the implementation in every browser you plan to support, as different browsers impose their own limitations on the size and quantity of cookies. Even if we've increased the amount of session data we can store as cookies, we're still limited to the maximum number of cookies a browser will accept per domain.

As a final note, storing sessions in the database may not impact performance as much as you think. It could be worth measuring the actual load created by this simple solution before investing many hours optimizing for a concern that might not be a real issue.

Answers 2

The browser has limitation of cookie. It's too large for the browser to store the cookie while you use cookie as the session driver. You can check the browser cookie limitation here http://browsercookielimits.squawky.net/

Recommendation:
Use redis as the session driver.

Steps:
1.After installed Redis server, add the predis/predis package:

composer require predis/predis 

2.Change your configuration file config/database.php:

'redis' => [      'client' => 'predis',      'default' => [         'host' => env('REDIS_HOST', 'localhost'),         'password' => env('REDIS_PASSWORD', null),         'port' => env('REDIS_PORT', 6379),         'database' => 0,     ],  ], 
Read More

Sunday, October 22, 2017

Socket.io not sharing sessions with express

Leave a Comment

I can't figure why this is happening exactly since it was working perfectly before.

I'm using the following libraries:

"express-socket.io-session": "^1.3.2" "socket.io": "^2.0.3" "express": "^4.15.4" "express-session": "^1.15.5" 

1 - I login the user via a http request and send the cookie back to the frontend. All operations on http work perfectly with the cookie in frontend backend exchanges.

2 - After the user is logged in I tell the frontend "ok, user is logged in, now connect to the sockets":

io ( this.url ); 

Here is the relevant code:

var io_session = require("express-socket.io-session"); var e_session = require("express-session");  var sessionFileStore = require('session-file-store')(e_session);  var ee_session = e_session({     store: new sessionFileStore({ path: './user_sessions' }),     secret: "something-random",     resave: true,     saveUninitialized: true });  var enableCORS = function(req, res, next) {     res.header('Access-Control-Allow-Origin', req.headers.origin);     res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');     res.header('Access-Control-Allow-Headers', 'Origin, Accept, Content-Type, Authorization, Content-Length, X-Requested-With, *');     res.header('Access-Control-Allow-Credentials',true);          // intercept OPTIONS method     if ('OPTIONS' == req.method) {         res.sendStatus(200);     } else {         next();     }; };  app.use(function(req, res, next) {         ///    next(); });  //app.use(cookieParser()); app.use(enableCORS); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.use(ee_session);  preparedApp = require("http").Server(app);  var io = require("socket.io")(preparedApp);  io.use(io_session(e_session,{     autoSave: true }));  preparedApp.listen(8080, function(){});  io.on('connection', function(socket){      var socket_session = socket.handshake.session,         socket_session_id = socket.handshake.sessionID;      console.log("SOCKET_SESSION:",socket_session);     console.log("SOCKET_SESSION_ID:",socket_session_id);      (...) 

3 - socket_session is 'empty' but everything in the regular http cookies works. The session is maintained there.

One thing I noticed is that socket_session_id points to a session file that does not exist inside the folder user_sessions. The only ones that exist are created in the http login. So, basically:

=> User logins: efihaeif939311kf3f3 session id file is created.

=> Socket connects: fiaejgieofaekofek is the session id in the same login flow but file does not exist in user_sessions (note that the session id is not the same)

Any idea on why this is happening? I have absolutely no idea.

Thanks

1 Answers

Answers 1

try sharing the session by stringifying the session json and retrieving and parsing back to session object in the client side.

This might work.

Read More

Monday, October 9, 2017

Can't make lusca CSRF work with https: 403 forbidden

Leave a Comment

This is driving me nuts. I have tried reading the lusca source code but found it hard to understand.

Checked several examples too, but since each config is different, and the only debugging output I have are two strings to compare, I'd better ask for some help!

Here's the code server side:

app.use([ cookieParser(process.env.SESSION_SECRET), session({   resave: false,   saveUninitialized: true,   secret: process.env.SESSION_SECRET,   store: new MongoStore({ url: MONGO_URL, autoReconnect: true }),   cookie: {     secure: process.env.NODE_ENV === 'production'   }, }), lusca({   csrf: true,   xframe: 'SAMEORIGIN',   xssProtection: true, })]); 

And from the clientside, I send Ajax POST requests with the x-csrf-token:l0gH3xmssge53E/p2NsJ4dGnHaSLdPeZ+bEWs= header in it:

fetch(url, {   method: 'POST',   credentials: 'include',   headers: {     'x-csrf-token': CSRF_TOKEN   } }); 

Crazy thing is, it's working locally, but as soon as I go https in production, I get the 403 Forbidden error message.

Here are the versions I use:

"cookie-parser": "1.4.3", "express-session": "1.15.3", "lusca": "1.5.1", 

Also I read this from the express/session doc:

Note Since version 1.5.0, the cookie-parser middleware no longer needs to be used for this module to work.

But as far as I'm concerned, I need to store some persistent ID of the users (longer than the session). I need to use cookies for that, right?

I'd like to understand better on the whole session/cookie thing, but until now I never found any useful resource on the topic.

Thanks!

1 Answers

Answers 1

If you are running your Node.js server behind a proxy you will need to set trust proxy to true:

var isProductionEnv = process.env.NODE_ENV === 'production';  app.use([ cookieParser(process.env.SESSION_SECRET), session({   resave: false,   saveUninitialized: true,   secret: process.env.SESSION_SECRET,   store: new MongoStore({ url: MONGO_URL, autoReconnect: true }),   proxy: isProductionEnv,   cookie: {     secure:isPrudictionEnv,   }, }), lusca({   csrf: true,   xframe: 'SAMEORIGIN',   xssProtection: true, })]);   app.set('trust proxy', isProductionEnv); 

Check out this stack overflow answer. Also check out this page on Express behind proxies.

Read More

Friday, October 6, 2017

Postman is not using cookie

Leave a Comment

I've been using Postman in my app development for some time and never had any issues. I typically use it with Google Chrome while I debug my ASP.NET API code.

About a month or so ago, I started having problems where Postman doesn't seem to send the cookie my site issued.

Through Fiddler, I inspect the call I'm making to my API and see that Postman is NOT sending the cookie issued by my API app. It's sending other cookies but not the one it is supposed to send -- see below:

enter image description here

Under "Cookies", I do see the cookie I issue i.e. .AspNetCore.mysite_cookie -- see below:

enter image description here

Any idea why this might be happening?

P.S. I think this issue started after I made some changes to my code to name my cookie. My API app uses social authentication and I decided to name both cookies i.e. the one I receive from Facebook/Google/LinkedIn once the user is authenticated and the one I issue to authenticated users. I call the cookie I get from social sites social_auth_cookie and the one I issue is named mysite_cookie. I think this has something to do with this issue I'm having.

1 Answers

Answers 1

I have been running into this issue recently with ASP.NET core 2.0. ASP.NET Core 1.1 however seems to be working just fine and the cookies are getting set in Postman

Read More

Wednesday, September 6, 2017

React frontend and REST API, CSRF

Leave a Comment

React frontend with REST API as backend, authorisation by JWT, but how to handle session ? For example after login i get JWT token from REST, if i save it to localStorage i am vulnerable to XSS, if i save it to Cookies, same problems only if am not setting HttpOnly, but react can't read HttpOnly Cookies (i need to read cookie to take jwt from it, and use this jwt with rest requests), also i didn't mention CSRF problem, if you using REST as backend, you can't use CSRF Token.

As a result React with REST seems like bad solution and i need to rethink my architecture, how to be? Is it possible to offer your users secure react application what have all business logic handled on REST API side without fear to lose their data?

Update:

As far as i understood, there is possibility to do this:

  1. React makes AJAX call to REST API
  2. React gets JWT token from REST
  3. React writes httponly cookie
  4. Because react can't read httponly cookie, we use it as-is in our all REST call where we need authentication
  5. REST on calls checks XMLHttpRequest header, what is some kind of CSRF protection
  6. REST side check for cookie, read JWT from it and do stuff

I have lack of theoretical knowledge here, but looks logic and pretty secure, but i still need an answer to my questions and approve of this "workflow".

2 Answers

Answers 1

1.React makes AJAX call to REST API

assured, lots of restful resource client lib available

2.React gets JWT token from REST

assured, this is what JWT should do

3.React writes httponly cookie

I don't think so, It should not work, but session is not such a important thing, it'll soon get out of date, and recheck password on key operations, even the hackers got it in a very shot time, you can bind session token together with IP when user login and check it in your backend apis. If you want it most secured, just keep token in memory, and redo login when open new page or page refreshes

4.Because react can't read httponly cookie, we use it as-is in our all REST call where we need authentication

assured, check user and permissions through login token, like csrf you can put your login token into your request header, and check it in your backend apis. Bind login token to your own restful lib will save you a lot codes

5.REST on calls checks XMLHttpRequest header, what is some kind of CSRF protection REST side check for cookie, read JWT from it and do stuff

assured, as most people do. Also, bind csrf token to your own restful lib will save you a lot codes

use user token in header https://www.npmjs.com/package/express-jwt-token Authorization JWT < jwt token >

use csrf token in header https://github.com/expressjs/csurf req.headers['csrf-token'] - the CSRF-Token HTTP request header.

restful client https://github.com/cujojs/rest

react with jwt https://github.com/joshgeller/react-redux-jwt-auth-example

Answers 2

Your server can set the JWT cookie directly as a response to the login request.

The server responds to POST /login with Set-Cookie: JWT=xxxxxx. That cookie is http only and therefore not vulnerable to XSS, and will be automatically included on all fetch requests from the client (as long as you use withCredentials: true).

CSRF is mitigated as you mentioned, see OWASP for details.

Read More

Tuesday, August 1, 2017

Browser not keeping cookie from response header

Leave a Comment

I am trying to do something supposedly simple and easy: set a cookie! But the browser (Chrome and Safari tested) is simply ignoring them. So the response headers look like:

Access-Control-Allow-Credentials:true Access-Control-Allow-Origin:* Connection:keep-alive Content-Encoding:gzip Content-Type:application/json; charset=utf-8 Date:Wed, 19 Jul 2017 04:51:51 GMT Server:nginx Set-Cookie:UserAuth=<some jwt>; Path=/; Domain=10.10.1.110; Expires=Wed, 19 Jul 2017 12:51:51 GMT; HttpOnly; Secure Transfer-Encoding:chunked Vary:Origin 

The request does include withCredentials=true. But the cookies section in Chrome is empty. I've tried removing the domain altogether, removing the path, every configuration I can think of, but the browser just won't play ball.

What am I missing?

2 Answers

Answers 1

Your cookie showing HttpOnly; Secure;

Using the HttpOnly flag when generating a cookie helps mitigate the risk of client side script accessing the protected cookie

The purpose of the secure flag is to prevent cookies from being observed by unauthorized parties due to the transmission of a the cookie in clear text. By setting the secure flag, the browser will prevent the transmission of a cookie over an unencrypted channel.

Cookies will be interrupted if travel through HTTP with secure flag in TLS layer. So check your preference and set the configuration of cookies accordingly.

Answers 2

If you don't use HTTPS for 10.10.1.110 then don't use secure for set-cookie header. I tested: it works fine without secure attribute.

Set-Cookie:UserAuth=<some jwt>; Path=/; Domain=10.10.1.110; Expires=Wed, 19 Jul 2017 12:51:51 GMT; HttpOnly 

I find this note in the MDN:

Note: Insecure sites (http:) can't set cookies with the "secure" directive anymore (new in Chrome 52+ and Firefox 52+).

Read More

Friday, July 14, 2017

How to put Cookie session id into volley request?

Leave a Comment

So, i have this code to make a POST request with volley:

public class MainActivity extends AppCompatActivity {   Button btnSearch;  ProgressDialog loadingDialog;  ListView lvResult;  String session_id;  RequestQueue queue;  MyCookieManager myCookieManager;   @Override  protected void onCreate(Bundle savedInstanceState) {   super.onCreate(savedInstanceState);   setContentView(R.layout.activity_main);    btnSearch = (Button) findViewById(R.id.btnSearch);   lvResult = (ListView) findViewById(R.id.lvResult);   loadingDialog = new ProgressDialog(MainActivity.this);   loadingDialog.setMessage("Wait.\nLoading...");   loadingDialog.setCancelable(false);   myCookieManager = new MyCookieManager();    requestCookie(); //FIRST CALL TO GET SESSION ID    btnSearch.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View view) {     showLoading();     requestWithSomeHttpHeaders(); //CALL TO MAKE THE REQUEST WITH VALID SESSION ID    }   });   }   public void requestCookie() {   queue = Volley.newRequestQueue(this);   String url = "http://bid.cbf.com.br/a/bid/carregar/json/";    StringRequest postRequest = new StringRequest(Request.Method.POST, url,    new Response.Listener < String > () {     @Override     public void onResponse(String response) {      //      String x = myCookieManager.getCookieValue();     }    },    new Response.ErrorListener() {     @Override     public void onErrorResponse(VolleyError error) {      Log.d("ERRO", "Erro => " + error.toString());      hideLoading();     }    }   ) {    @Override    public byte[] getBody() throws AuthFailureError {     String httpPostBody = "uf=PE&dt_pesquisa=23/05/2017&tp_contrato=TODOS&n_atleta=&codigo_clube=&exercicio=";     return httpPostBody.getBytes();    }     @Override    public Map < String, String > getHeaders() throws AuthFailureError {     Map < String, String > params = new HashMap < String, String > ();     params.put("User-Agent", "Mozilla/5.0");     params.put("Accept", "application/json, text/javascript, */*; q=0.01");     params.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");     //params.put("Set-Cookie", session_id);// + " _ga=GA1.3.1300076726.1496455105; _gid=GA1.3.1624400465.1496455105; _gat=1; _gali=AguardeButton");     //"PHPSESSID=ra0nbm0l22gsnl6s4jo0qkqci1");     return params;    }     protected Response < String > parseNetworkResponse(NetworkResponse response) {     try {      String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));      String header_response = String.valueOf(response.headers.values());      int index1 = header_response.indexOf("PHPSESSID=");      int index2 = header_response.indexOf("; path");      //Log.e(Utils.tag, "error is : " + index1 + "::" + index2);      session_id = header_response.substring(index1, index2);       return Response.success(jsonString, HttpHeaderParser.parseCacheHeaders(response));     } catch (UnsupportedEncodingException e) {      return Response.error(new ParseError(e));     }    }   };    queue.add(postRequest);  }   public void requestWithSomeHttpHeaders() {   queue = Volley.newRequestQueue(this);   String url = "http://bid.cbf.com.br/a/bid/carregar/json/";    StringRequest postRequest = new StringRequest(Request.Method.POST, url,    new Response.Listener < String > () {     @Override     public void onResponse(String response) {      Log.d("Response", response);      String x = myCookieManager.getCookieValue();      String status = "";       try {       JSONObject resultObject = new JSONObject(response);       Log.d("JSON RESULT =>", resultObject.toString());      } catch (JSONException e) {       Toast.makeText(MainActivity.this, "Request Error", Toast.LENGTH_SHORT).show();       e.printStackTrace();      }       hideLoading();     }    },    new Response.ErrorListener() {     @Override     public void onErrorResponse(VolleyError error) {      Log.d("ERROR", "Error => " + error.toString());      hideLoading();     }    }   ) {    @Override    public byte[] getBody() throws AuthFailureError {     String httpPostBody = "uf=PE&dt_pesquisa=23/05/2017&tp_contrato=TODOS&n_atleta=&codigo_clube=&exercicio=";     return httpPostBody.getBytes();    }     @Override    public Map < String, String > getHeaders() throws AuthFailureError {     Map < String, String > params = new HashMap < String, String > ();     params.put("User-Agent", "Mozilla/5.0");     params.put("Accept", "application/json, text/javascript, */*; q=0.01");     params.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");     params.put("Cookie", /*myCookieManager.getCookieValue()*/ session_id + "; _ga=GA1.3.1300076726.1496455105; _gid=GA1.3.1624400465.1496455105; _gat=1; _gali=AguardeButton");     return params;    }   };    queue.add(postRequest);  }   private void showLoading() {   runOnUiThread(new Runnable() {    @Override    public void run() {     if (!loadingDialog.isShowing())      loadingDialog.show();    }   });  }   private void hideLoading() {   runOnUiThread(new Runnable() {    @Override    public void run() {     if (loadingDialog.isShowing())      loadingDialog.dismiss();    }   });  } } 

If I send a valid cookie ID this return a valid JSON object else a empty object.

I tried (unsuccessfully) to set default cookie handles like

CookieManager manager = new CookieManager(); CookieHandler.setDefault(manager);

but I get a empty object.

How to put a valid cookie session ID to post request?

3 Answers

Answers 1

So the problem was getting a valid cookie. My mistake was to get it from the POST request itself. I kept the same working principle, getting the cookie when I started the application but using GET instead of POST and calling the root of the URL instead of the address where I get the JSON. My solution looked like this:

public void requestCookie() {   queue = Volley.newRequestQueue(this);   String url = "http://bid.cbf.com.br/";    StringRequest getRequest = new StringRequest(Request.Method.GET, url,    new Response.Listener < String > () {     @Override     public void onResponse(String response) {      String x = myCookieManager.getCookieValue();     }    },    new Response.ErrorListener() {     @Override     public void onErrorResponse(VolleyError error) {      Log.d("ERROR", "Error => " + error.toString());      hideLoading();     }    }   ) {       protected Response < String > parseNetworkResponse(NetworkResponse response) {     try {      String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));      String header_response = String.valueOf(response.headers.values());      int index1 = header_response.indexOf("PHPSESSID=");      int index2 = header_response.indexOf("; path");      //Log.e(Utils.tag, "error is : " + index1 + "::" + index2);      session_id = header_response.substring(index1, index2);       return Response.success(jsonString, HttpHeaderParser.parseCacheHeaders(response));     } catch (UnsupportedEncodingException e) {      return Response.error(new ParseError(e));     }    }   };    queue.add(getRequest);  } 

Answers 2

You getting cookie (Session id) in Login response, Save cookie in sharedpreference or other db, and use that to send in request.

for getting cookie from login request

 CustomStringRequest stringRequest = new CustomStringRequest(Request.Method.POST, SIGN_IN_URL,                     new Response.Listener<CustomStringRequest.ResponseM>() {                         @Override                         public void onResponse(CustomStringRequest.ResponseM result) {                              CookieManager cookieManage = new CookieManager();                             CookieHandler.setDefault(cookieManage);                              progressDialog.hide();                             try {                                 //From here you will get headers                                 String sessionId = result.headers.get("Set-Cookie");                                 String responseString = result.response;                                  Log.e("session", sessionId);                                 Log.e("responseString", responseString);                                  JSONObject object = new JSONObject(responseString); 

CustomStringRequest class

public class CustomStringRequest extends Request<CustomStringRequest.ResponseM> {       private Response.Listener<CustomStringRequest.ResponseM> mListener;      public CustomStringRequest(int method, String url, Response.Listener<CustomStringRequest.ResponseM> responseListener, Response.ErrorListener listener) {         super(method, url, listener);         this.mListener = responseListener;     }       @Override     protected void deliverResponse(ResponseM response) {         this.mListener.onResponse(response);     }      @Override     protected Response<ResponseM> parseNetworkResponse(NetworkResponse response) {         String parsed;         try {             parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));         } catch (UnsupportedEncodingException e) {             parsed = new String(response.data);         }          ResponseM responseM = new ResponseM();         responseM.headers = response.headers;         responseM.response = parsed;          return Response.success(responseM, HttpHeaderParser.parseCacheHeaders(response));     }       public static class ResponseM {         public Map<String, String> headers;         public String response;     }  } 

set cookie when add request to server..

                @Override                 public Map<String, String> getHeaders() throws AuthFailureError {                     Map<String, String> headers = new HashMap<String, String>();                      String session=sharedPreferences.getString("sessionId","");                     headers.put("Cookie",session);                     return headers;                 } 

Answers 3

Using cookies with Android volley library

Request class:

public class StringRequest extends com.android.volley.toolbox.StringRequest {      private final Map<String, String> _params;      /**      * @param method      * @param url      * @param params      *            A {@link HashMap} to post with the request. Null is allowed      *            and indicates no parameters will be posted along with request.      * @param listener      * @param errorListener      */     public StringRequest(int method, String url, Map<String, String> params, Listener<String> listener,             ErrorListener errorListener) {         super(method, url, listener, errorListener);          _params = params;     }      @Override     protected Map<String, String> getParams() {         return _params;     }      /* (non-Javadoc)      * @see com.android.volley.toolbox.StringRequest#parseNetworkResponse(com.android.volley.NetworkResponse)      */     @Override     protected Response<String> parseNetworkResponse(NetworkResponse response) {         // since we don't know which of the two underlying network vehicles         // will Volley use, we have to handle and store session cookies manually         MyApp.get().checkSessionCookie(response.headers);          return super.parseNetworkResponse(response);     }      /* (non-Javadoc)      * @see com.android.volley.Request#getHeaders()      */     @Override     public Map<String, String> getHeaders() throws AuthFailureError {         Map<String, String> headers = super.getHeaders();          if (headers == null                 || headers.equals(Collections.emptyMap())) {             headers = new HashMap<String, String>();         }          MyApp.get().addSessionCookie(headers);          return headers;     } } 

MyApp:

public class MyApp extends Application {     private static final String SET_COOKIE_KEY = "Set-Cookie";     private static final String COOKIE_KEY = "Cookie";     private static final String SESSION_COOKIE = "sessionid";      private static MyApp _instance;   private RequestQueue _requestQueue;   private SharedPreferences _preferences;      public static MyApp get() {         return _instance;     }      @Override     public void onCreate() {         super.onCreate();         _instance = this;             _preferences = PreferenceManager.getDefaultSharedPreferences(this);         _requestQueue = Volley.newRequestQueue(this);     }      public RequestQueue getRequestQueue() {         return _requestQueue;     }       /**      * Checks the response headers for session cookie and saves it      * if it finds it.      * @param headers Response Headers.      */     public final void checkSessionCookie(Map<String, String> headers) {         if (headers.containsKey(SET_COOKIE_KEY)                 && headers.get(SET_COOKIE_KEY).startsWith(SESSION_COOKIE)) {                 String cookie = headers.get(SET_COOKIE_KEY);                 if (cookie.length() > 0) {                     String[] splitCookie = cookie.split(";");                     String[] splitSessionId = splitCookie[0].split("=");                     cookie = splitSessionId[1];                     Editor prefEditor = _preferences.edit();                     prefEditor.putString(SESSION_COOKIE, cookie);                     prefEditor.commit();                 }             }     }      /**      * Adds session cookie to headers if exists.      * @param headers      */     public final void addSessionCookie(Map<String, String> headers) {         String sessionId = _preferences.getString(SESSION_COOKIE, "");         if (sessionId.length() > 0) {             StringBuilder builder = new StringBuilder();             builder.append(SESSION_COOKIE);             builder.append("=");             builder.append(sessionId);             if (headers.containsKey(COOKIE_KEY)) {                 builder.append("; ");                 builder.append(headers.get(COOKIE_KEY));             }             headers.put(COOKIE_KEY, builder.toString());         }     }  } 
Read More