Session token expiring

I have a project in which all forms send a token, this token is saved in the session and the two are compared when the form is handled to make sure they match.

Everything is working well except that after 60 minutes, the session token gets reset.
I have tried to change the session duration through session.gc_maxlifetime but this didn’t work, and I do generally prefer that the session remain 1 hour.

At the moment I have an ajax request that is targeted towards a file that is used just for session refreshing, this ajax request runs every 45 minutes.
The purpose is to refresh the session every 45 minutes so that it does not expire after 60 minutes, but it is expiring anyway, so something is not working.

I think my issue is the session codes I have set up, but I cant figure out the actual issue, here is the code i use at the beginning of every page, which is also in the session refresh page:

session_refresh.php

//Start the session if not started already
if (session_status() == PHP_SESSION_NONE) {
	session_start();
	session_regenerate_id();
}

//Set a session start time
if (!isset($_SESSION['session_start_time'])) {
	$_SESSION['session_start_time'] = date('d-m-y, H:i:s');
}

//Create a session token
if (!isset($_SESSION['token']) || empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}

Here is the JS code I use, I have tested this and it does return the session when tested, but after 60 minutes, the token is different:

$(function(){
        setInterval(function(){
            $.get("session_refresh.php", function(data){
                // console.log(data);
            });
        }, 2700000);
    });
 });

Surprised it’s 60 minutes, to be honest.

PHP sessions by default time out after 15 minutes. You’ve set a timer for 45 minutes.

Every call to the page should be invoking session_start(), if you want the session to persist. “start” is a bit of a misnomer, as it starts a new or resumes an existing session.

Any session that wants to be persisted must call the refresh page on a timer lower than PHP’s configured session.gcmaxlifetime [default = 1440 seconds] or session.cookie_lifetime if cookies are used [default = 0, which is translated as ‘until the browser is closed.’].

There is no need to regenerate an ID that has been created microseconds earlier with session_start.

1 Like

I think I set it to 60 because of this specific issue, I tried to make it longer, but its still not working as well as it should since I cant seem to extend the session.
Once its all working though I will change it back to 15 minutes as I don’t need it to be longer than that really.

I removed session_regenerate_id(), but I still don’t have a fix to the issue.

The session max lifetime is set to 60 minutes, and I set the timer for the ajax script for every 45 minutes, that should extend the current session every 45 minutes, right?

I set the session length back to the default 1440 seconds, and i set the ajax interval to work every 10 minutes, but this isn’t helping, the session just isn’t being extended or refreshed.

Did you change the code to always call session_start?

You mean take it out of the session if statement?

I tried just having session_start without the if statement, but it actually gave me an error saying that a session has already started.

That means something else is calling session_start. If something has already called session_start, you can’t call it again in the same page load.

Then wouldn’t it be ok to keep it in the if statement? That way it is only started if it hasn’t already been started, no?

But in general, either way, session_start is being called every page load, so I’m not sure why the session isn’t refreshing.

What is the value of session.cookie_lifetime? It sounds to me like the session might still be alive, but the cookie is gone, so the user can’t identify itself as the owner of the session anymore and thus a new session starts.

You can obtain this value using ini_get('session.cookie_lifetime').

I see that ini_get('session.cookie_lifetime') is actually set to 0.

But, after further testing, i see the on a computer, the session is actually being refreshed, i updated the console output to show the current token, and the refresh time, and it was the same for a few hours:

So on a computer it seems to be working, but on a mobile phone it is not, the session is not being refreshed.

When i output ini_get('session.cookie_lifetime') it shows 0, but, now my question is why does it even work on the computer, and why doesn’t it work on a mobile phone?

To give a few more details about this, i have the website set up as PWA, and have installed it on my phone, when I leave it open for an hour or more, the form does not submit anymore because the tokens don’t match.

I believe I figured out the issue, but I don’t know what the solution is.

On computers everything is working fine because the browser remains open and in a active state, even when not used, so the timer that calls the ajax request to refresh the session is always running, so the session is always refreshed.

On a phone though (through a browser or the PWA app - which is a browser basically), while it does work when the page is open, once you switch to another app, after a few minutes the page becomes not active, so the ajax timer stops running and the session does not get refreshed.
If you close the phone screen then it stops right away.

When you go back to the page, the interval timer will continue running.

This means the the interval timer will only run when the tab is active, which does make sense, but it means that if a user comes back to the tab at a later time, their session wasnt refreshed so the tokens wont match.

I think i need a way to detect if a user has “re-activated” the tab, and if so then to either refresh the session right away, or to just refresh the page.
Or to keep the interval timer working in the background somehow.

I wonder how other app handle this since switching between apps is a pretty normal and regular use type of thing.

(btw, I tested this by inspecting the phone element by connecting my phone to my computer with USB)

I find it odd that it works with the Jquery ajax request on a computer. Most probably that is just due to low traffic to the server, resulting in low garbage collection.

Unless you pass the session id with the request, you are not guaranteed to get the same session id that you had in the last request. Since no one passes the session id by GET anymore (session hijacking), this means by a cookie.

Anyway, looking to solve this by polling in an attempt to keep the session alive is the wrong approach.

You could for example store a encrypted string in the cookie, which would automatically log the user back in (assuming it passes validation, again think hijacking and ensure you can secure against this).

Though these days, I would simply recommend that you setup a JWT authentication solution. That give you the benefit similar as above, where you can automatically login a user again within a set time range. I.e. for example within 6 hours since last activity etc.

Again, think hijacking as JWT is also vulnerable to that if not used properly.

Thanks for the reference, ill take a look at it.

For this issue though, you mean to store the token in a cookie instead of the session?

Why do you think keeping the session alive is the wrong approach?

Would this approach work and still be safe?
Have a token request function that will check what the current session token is, and if its different from what the form input currently has, then update the input with the new token.

This would run when the page is focused on again, since if the user leaves the app open in the background for an hour, the old token would have expired, so the new request will get the new token and update the form inputs.

Like this, as long as the page is open and active, the token will remain correct since the session is being refreshed every 10 minutes, and if the user switches apps and comes back hours later, the form input will update with the new session

The session id is already stored in a cookie and passed with each browser request. That is why I found it odd how the Ajax request you do is able to extend the session since you do not pass the cookie along with it.

The JWT could be in addition to the session, or you can base everything on the JWT. We have moved on to base authentication by it unless of course, the client has specific requirements for their project (i.e. legacy systems, etc.).

Though keep in mind that every project is different, so what is best in project A, might not be the best choice for project B.

However, you have a problem that you need to solve, which JWT will help sort, and with proper handling on your side, you can be certain that it is the same user making the new request.

For the PHP sessions, I am not saying it is bad to use them, but in your case, you have run into issues, where if you combine it with a JWT layer you can solve the issue.

What is important to remember is that a PHP session is supposed to help you identify additional requests by the same user on the server side, since PHP is stateless.

This means that the solution you mention would not be secure enough to ensure it was the same user making the request. The strategy you mention sounds like a perfect way to allow users with “criminal” ideas to impersonate other users.

If you do not store user information in the session, you are overthinking this, since the current session have no user identifiable information. I.e. it does not matter if my session id is A or B when I submit the form, as exactly the same would happen.

1 Like

An AJAX request is also just an HTTP request that also sends along any cookies you have. There’s no magic there. The only difference is that an AJAX request is initiated by javascript rather than by the main thread of the browser.

So I don’t think JWT is a solution here. It would be much easier to make sessions longer lived.

Also, I’m not sure what having a token is supposed to solve. If this is to protected against CSRF then you need a token in the form and a token in the session and then on form submission check that the tokens are the same.

1 Like

Yes this is exactly what i am doing.
In all forms on the page there is a hidden input with the token, when the form is submitted, the token gets sent along with the rest of the inputs, and it is compared against what is in the session, if both match, proceed, if not, don’t.

Right now i am extending the session by the ajax requests every 10 minutes, but, when a user on a mobile phone closes the browser or app or switches to another, the JavaScript interval stops running, and if the user comes back to the site after 1 hour, the token in the form is no longer what will be in the session when the user submits the form, since the token in the form is expired. When the user will submit the form, a new token will be created since the old session expired and a new one is created.

So the solution I thought of was to detect when the browser/site is active again (when a user goes back to the browser app and the site), and then to check what the token in the session is with an ajax request, and if its different from what’s in the forms, then to update all inputs that have the token with the new one.

Does that solution make sense?
(im not sure if there is even a way to detect when the page is active/focused again, its just an idea).

Edit:
After some research, i found this:

$(window).focus(function(){
...
});

This will run a script when the page is focused on again, so i could run the ajax script here to get the new session token.

Edit 2:
Here is the code i current have:

$(window).focus(function(){
    $.get("/ajax/refresh_session", function(data){
        var dataParse = JSON.parse(data),
            newToken = dataParse['token'],
            currentToken = $('input[name="form_token"]').val()[0];
           
        //If the tokens dont match, update all of them with the new one
        if (currentToken != newToken) {
            $('input[name="form_token"]').val(newToken);
        }
    });
});

The question is if this will still be secure?

I can’t think of why it wouldn’t be.

1 Like

Great!
After testing, it seems to be working very well and fixed the issue I was having, so if there are no security issues then this is good enough for now (unless there is a better way to achieve this).

Thanks everyone for the help!

My bad, I was thinking it worked similarly to a curl request since it was initiated by Javascript. Thanks for clarifying.

As @rpkamp mentioned earlier, it is secure in your case. All you do is utilize the session for the CSRF protection.

1 Like