5

I upgraded my Symfony application from Symfony 4.0.7 to Symfony 4.1 and after that AJAX calls are losing sessions values.

I have about 6 ajax requests called at the same time. First of them is going fine but others are losing session values. It happened only after migration to Symfony 4.1 and only for AJAX calls. Any ideas?

edit: It happens only with ajax called at the same time. WHen I add eg 100 miliseconds delay between calling ajax then all works fine.

edit2: it happens on 4 different servers. 2 dev servers, 1 test server and 1 live server. all of them run on NGINX and php7

nospor
  • 4,190
  • 1
  • 16
  • 25
  • What server env are you experiencing this on? Would be interesting to know if this is symfony's local one or apache or whatever. Would be even more interesting to know if this happens on different server instances / setups as that would indicate if it's the server's config or symfony you need to look at... – Bananaapple Jun 22 '18 at 08:01
  • @Bananaapple I've updated my post – nospor Jun 22 '18 at 11:13
  • Try running the local `php bin/console server:run` to see if you still get it. Also, since it's to do with JS try it on different browsers. – Bananaapple Jun 22 '18 at 12:30
  • Maybe this: https://symfony.com/blog/new-in-symfony-4-1-session-improvements – Alejandro Jun 23 '18 at 07:35
  • Happend to me in Laravel. I don't know how to fix this but this is happen let's say you do an ajax request and before finish that ajax request you do an another ajax request and it return before first ajax request then when the first ajax request finish session also will erased. so I had to wait to send second ajax request until first one finished – Supun Praneeth Jun 23 '18 at 08:21
  • @Bananaapple it was tested on different browsers and effect is the same – nospor Jun 25 '18 at 08:38
  • @Alex and how this will help me? I've read that articles many tuimes before and there is nothing directly connected to my problem. Only thing which we can learn from there that they changed sessions somehow and now I have the problem. – nospor Jun 25 '18 at 08:39
  • @SupunPraneeth as I said before: it is working fine on Symfony 4.0.7 so this is the problem of current version of symfony not php in general. As I also said before it works fine when I add delay between AJAXs but I need those AJAX to be run more less at the same time. – nospor Jun 25 '18 at 08:41
  • @nospor, sorry if this is not helpful, I thought you could at least check that all your code have `hasSession()`. It's very strange that it doesn't work only in case of the same time, it looks like you've some additional config for js set up. How do you do ajax call? – Alejandro Jun 25 '18 at 10:20
  • I feel like it would work in case `setTimeout( doAjaxCall, 0)` - it's probably workaround for you – Alejandro Jun 25 '18 at 10:21
  • @Alex ajax calls are really simple ajaxs one after another. Setting delay for 0 will not work: **as I said in my first post** it works only for bigger delays like 100ms and this is depending on response time so it is not a constant number and this is not a workaround for my situation – nospor Jun 25 '18 at 10:40
  • I though I was loosing it, this is the first topic with a description which matches my problem. I have a bit more tests done: https://stackoverflow.com/questions/53188446/session-values-randomly-gone-back – Martijn Nov 13 '18 at 10:43

4 Answers4

1

Possible Causes could be, as follows::

Allow to cache requests that use sessions:

Whenever the session is started during a request, Symfony turns the response into a private non-cacheable response to prevent leaking private information. However, even requests making use of the session can be cached under some circumstances.

For example, information related to some user group could be cached for all the users belonging to that group. Handling these advanced caching scenarios is out of the scope of Symfony, but they can be solved with the FOSHttpCacheBundle.

In order to disable the default Symfony behavior that makes requests using the session uncacheable, in Symfony 4.1 we added the NO_AUTO_CACHE_CONTROL_HEADER header that you can add to response:

use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
$response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, 'true');

Deprecate some uses of Request::getSession()

Using Request::getSession() when no session exists has been deprecated in Symfony 4.1 and it will throw an exception in Symfony 5.0. The solution is to always check first if a session exists with the Request::hasSession() method:

if ($request->hasSession() && ($session = $request->getSession())) {
    $session->set('some_key', 'some_value');
}

More on Ref: Here.

Amit Verma
  • 8,660
  • 8
  • 35
  • 40
1

Have you check XMLHttpRequest.withCredentials?

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials

What about the response Headers?

  1. Access-Control-Allow-Credentials: true
  2. Connection: keep-alive
  3. set-cookie: ...

Are these requests' session id equal?

http://php.net/manual/en/function.session-id.php

Maybe you need set the session cookie before ajax request.

Here is a example:

ajax.php

<?php
function getSessionIdentifier()
{
    if (!session_id()) {
        session_start();
    }
    if (!isset($_SESSION['identifier'])) {
        $_SESSION['identifier'] = bin2hex(random_bytes(5));
    }
    return $_SESSION['identifier'];
}

echo json_encode([
    'identifier' => getSessionIdentifier()
]);

start-without-session.php

<!DOCTYPE html>
<html>
<head><title>start without session</title></head>
<body>
<script>
let started;
const cookie = () => '[' + document.cookie + ']';
const track = () => {
    started = started || (new Date()).getTime();
    return ((new Date()).getTime() - started) + 'ms';
};

const send = (index) => {
    const req = new XMLHttpRequest();
    req.open('GET', '/ajax.php');

    console.log(track(), 'send', index, cookie());

    req.addEventListener("load", () => {
        console.log(track(), 'receive', index, cookie(), req.responseText);
    });

    req.send();
}

document.cookie = "PHPSESSID=;expires=Thu, 01 Jan 1970 00:00:00 GMT";

console.log(track(), 'begin', cookie());
const len1 = 5;
const len2 = 10;
Array.from({length: len1}).forEach((v, i) => send(i));
console.log(track(), 'delay');
Array.from({length: len2}).forEach((v, j) => window.setTimeout(() => send(j + len1), j * 10));
</script>
</body></html>

enter image description here

start-with-session.php

<?php
session_start();
$_SESSION['identifier'] = bin2hex(random_bytes(5));
?><!DOCTYPE html>
<html>
<head><title>start with session</title></head>
<body>
<script>
let started;
const cookie = () => '[' + document.cookie + ']';
const track = () => {
    started = started || (new Date()).getTime();
    return ((new Date()).getTime() - started) + 'ms';
};

const send = (index) => {
    const req = new XMLHttpRequest();
    req.open('GET', '/ajax.php');

    console.log(track(), 'send', index, cookie());

    req.addEventListener("load", () => {
        console.log(track(), 'receive', index, cookie(), req.responseText);
    });

    req.send();
}

//
// document.cookie = "PHPSESSID=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
//

console.log(track(), 'begin', cookie());
const len1 = 5;
const len2 = 10;
Array.from({length: len1}).forEach((v, i) => send(i));
console.log(track(), 'delay');
Array.from({length: len2}).forEach((v, j) => window.setTimeout(() => send(j + len1), j * 10));
</script>
</body></html>

enter image description here

I'm not 100% sure this is the same situation. need more info.

Anthony Zhan
  • 861
  • 8
  • 11
  • So, do you suggest that when I send ajax with delays cookies are sent automatically and when I send ajax exactly the same way, except that I do not do any delays, I need to set cookie manually? – nospor Jun 27 '18 at 10:38
  • In php session_start() will set a cookie named PHPSESSID automatically. This cookie is used to identify session. – Anthony Zhan Jun 27 '18 at 11:01
  • XMLHttpRequest.withCredentials tell the browser send ajax request with this session cookie. – Anthony Zhan Jun 27 '18 at 11:04
  • I know that. THats why I am asking you: so are you saying that when I send ajax one after another I need to set it manually? Because as I said few times before, when I sent AJAXs with delays all is working fine without setting any additional things – nospor Jun 27 '18 at 11:11
  • no. What I'm saying is that you need set the session cookie before all these ajax requests. And all these ajax request send with same session cookie. – Anthony Zhan Jun 27 '18 at 11:21
  • So I am asking why I do not have to set this when I call AJAXs with delays? – nospor Jun 27 '18 at 12:30
  • @nospor , I added more details in the answer. But I'm not sure this is the same situation as yours. We need more info. – Anthony Zhan Jun 28 '18 at 05:53
  • Thank you for your update Anthony. I JQuery to send AJAX. It sends cookies automatically with every request. I still think this is a fault of Symfony upgrade, especially that in 4.1 they did some session changes. With Symfony 4.0.7 this immediate ajax works perfectly fine. I do not change ajax calls. What I change is SF version and then it stops to work. – nospor Jun 28 '18 at 14:26
1

Ok, so the problem was because of session fixation strategy which was changing session ID each request and AJAX requests each after another did have time to update the new ID.

A solution was quite simple, just setting session_fixation_strategy: none.

nospor
  • 4,190
  • 1
  • 16
  • 25
0

You must not launch all the AJAX requests on the same time when your application depends on live cookies. Maybe the symfony uses something in the cookies to do the CSRF Token work.

The reason why it worked with a 100ms timeout between the request is that the first one had time to parse the response and change the cookies with that response, and the next request used the new cookies.

what you need to do is:

  • make the second AJAX call a callback from the first, the third one a callback from the second and so on...

OR

  • find out what is in the cookies that needs to be up to date to not generate an error and disable it. But take in count that this protection is in place for a reason and you might expose your application to a security breach if disabled. it might help us figuring the issue if you provide the faulty AJAX calls responses (inpect->network in browser)

OR

  • Make your ajax call use JWT based middleware (witch is complicated if you never done it before) so it uses a stateless session.
N69S
  • 16,110
  • 3
  • 22
  • 36
  • Simple question: why it does work with symfony 4.0.7 and after upgrade to symfony 4.1 it stop to work? For me this is symfony bug not ajax. – nospor Jun 28 '18 at 14:15
  • ad1) I can not do this. Those ajax calls are for charts data. Some charts can generate quicker than others. I can not generate chart after chart because it can take much longer time to display all charts. ad2) I need that missing data for authentication. I can not ignore them. ad3) Yes, that could be a solution but still I think this is SF4.1 upgrade fault and I do not want to waste time to introduce JWT when is not needed – nospor Jun 28 '18 at 14:29
  • ad1) loading the charts one by one from the same symfony installation is faster than loading all of them at once. doing this, you also avoid risking a timeout on one of the ajax request. Try Benchmarking the two methods. you will be surprised by the results – N69S Jun 29 '18 at 13:33
  • With that solution clients will have to wait longer to see charts. The idea is that they can see some "faster" charts earlier if possible and it worked perfectly fine for Sf 4.0.7 – nospor Jul 02 '18 at 13:39