How to find out that session has expired in PHP? - php

I'm trying to improve the session management for web applications. My major issue is the session expiration and how to deal with it. For that I'd like to find out if the session is still available or not. I'm using the default file based sessions (PHP 7.1, Apache 2.4, Fedora/RHEL) and it is cookie based.
What I've found out is that the session GC gets executed when session_start() is called. It is not with the begin or end of the script execution, it happens with this function. What seems odd to me is that if session_start() and the GC consider the session as expired and want to delete the corresponding session file, $_SESSION gets populated regularly first, then the file will be deleted. That's surprising.
With that behaviour only the next following request leads to an empty $_SESSION. I would expect this with the call before - the one that deletes the file. So if I'd like to know whether the session has expired in the current request I would have to check if the session file still exists after the session_start() call. That seems strange to me.
Are there any other or better ways to check that a session has expired than looking into the file system?
I thought I could just check if $_SESSION is empty to determine that the session was renewed - but that is obviously not possible.
Update: I've found the following bug reports dealing with the issue: this and that. There's also a SO entry about the expiration problem. Current PHP source: php_session_start calls php_session_initialize calls php_session_gc.
You may want to play with this script (unreal settings are just for testing purposes):
ini_set('session.gc_maxlifetime', 2); // Session gets expired after 2 seconds
ini_set('session.gc_divisor', 1); // Delete expired session files immediately
ini_set('session.save_path', '/some/safe/path/for/testing'); // Must be accessible for the server
//ini_set('session.use_strict_mode', true); // Uncomment if the id should be renewed, what makes no difference here
echo "Session files before session_start call<br>";
listSessionFiles();
session_start();
echo "Session files after session_start call<br>";
listSessionFiles();
echo "<hr>";
echo "Session id: " . session_id() . "<br>";
echo "Session content: " . print_r($_SESSION, true);
$_SESSION['x'] = time(); // Populate the session with something
function listSessionFiles() {
echo "<ul>";
$none = true;
$dir = dir(ini_get('session.save_path'));
while ($entry = $dir->read()) {
if (preg_match('/^sess_/', $entry)) {
echo "<li>" . $entry . "</li>";
$none = false;
}
}
$dir->close();
if ($none) echo "<li>None</li>";
echo "</ul>";
}
Just reload the page some times. Wait at least more than two seconds. Otherwise the session does not expire.

One way to circumvent the problem is to use cookies with a certain lifetime (below the session lifetime). If the cookie expires it won't be sent to the server. PHP will then create a new session when session_start() is called, so $_SESSION will be empty.
This might be enough to find out that the session is not available. Although one cannot distinguish in PHP if the session has expired or anything went wrong. You can just tell that no session data is available and do appropiate things then (amongst other also destroy the newly created empty session).

Related

PHP session, why is session_start() required multiple times?

I am writing a web application that saves POSTed data to a session in one page, then redirects to another page to utilize the created session information. This was after I read that the proper way to process data and display data is to separate them into two different scripts so as not to run into a redundant $_POST data issue. That is, not to keep $_POSTing the same data to the server every page refresh.
I have a view page, index.php, and a data processing page, setDate.php. When viewing index.php, the user can choose to set $_POST['month'] and $_POST['year'] variables via an input form, and submit them to setDate to assign $_SESSION['desMonth'] and $_SESSION['desYear'] respectively.
It wasn't until I added a second (IMO redundant) session_start(); declaration on setDate.php that the code started to work the way I wanted to. Without it, it was as if index.php was ignoring setDate.php's $_SESSION[*] modifications completely.
Why do I have to define this redundant session_start(); if I already started the session (and received the PHPSESSID cookie) on the initial index.php where the $_SESSION[*] data is being used?
Here are some working code snippets:
setDate.php
<?php
require_once 'jan.php';
session_start();
//get the requested month and years to view (iterative).
if(isset($_POST['nextMonth']) && filter_var($_POST['nextMonth'], FILTER_SANITIZE_NUMBER_INT)) { //this filter only allows +- and 0-9
$_SESSION['desMonth'] += sanitizeInput($_POST['nextMonth']);
if($_SESSION['desMonth'] > 12) {
$_SESSION['desMonth'] = $_SESSION['desMonth']-12;
$_SESSION['desYear'] += 1;
}
else if($_SESSION['desMonth'] < 1) {
$_SESSION['desMonth'] = 12;
$_SESSION['desYear'] -= 1;
}
}
//get the explicit month and years to view.
if(isset($_POST['month']) && filter_var($_POST['month'], FILTER_SANITIZE_NUMBER_INT)) {
$_SESSION['desMonth'] = sanitizeInput($_POST['month']);
echo "set month";
}
if(isset($_POST['year']) && filter_var($_POST['year'], FILTER_SANITIZE_NUMBER_INT)) {
$_SESSION['desYear'] = sanitizeInput($_POST['year']);
echo "set year";
}
echo $_SESSION['desMonth'];
echo $_SESSION['desYear'];
header("Location: /");
die();
?>
Truncated index.php
<?php
session_start();
require_once 'cellUpdater.php';
$timeForDateUse = mktime(1,1,10,$_SESSION['desMonth'],1,$_SESSION['desYear']); //this line is used for various formatting below.
...
Without the session_start(); declaration in setDate.php the $_SESSION[*] data will not be preserved. Why?
EDIT: Question answered, editing for imaginary internet points
From php.net:
session_start() creates a session or resumes the current one based on
a session identifier passed via a GET or POST request, or passed via a
cookie.
When session_start() is called or when a session auto starts, PHP will
call the open and read session save handlers.
In other words, session_start() does not only create a session when a session does not exists yet, but it also makes it possible for a script to access the current session. It gives read and write access to the $_SESSION variable.
Without session_start, the script cannot write or read from the session, the session is still there but it cannot be read or modified by the script. If you only want to give read access to a session you can call session_write_close(); to close the write access. This can be handy when you want multiple files to open the same session at the same time. When a script has write access it blocks the current session file, blocking all other scripts that want write access to the same session.
If you are lazy and always want a session to be active, you can write
php_flag session.auto_start 1
in a .htaccess file to enable the auto start of a session in php.

How can I user session_start() in a loop with no warning/error?

I am trying to implement a Server-Sent Event where the server will send the clients new messages.
The challenge here is that the server updated sessions and then echo their content.
The problem is that I will need to have that in one infinite loop.
Here is what I have done.
while(true){
session_start();
header("Content-Type: text/event-stream" . PHP_EOL);
header("Cache-Control: no-cache" . PHP_EOL);
//configure the connection
$conf = new ICWS\Config\Config($url, $stationName);
//create a new instance of icws
$attrebutes = array('AccoRDI_mid','AccoRDI_account_id');
$icws = new ICWS\Connection($conf, $attrebutes, true);
$messaging = new ICWS\Messaging($icws);
$messaging->processMessages();
session_write_close();
$result = $messaging->getCallsQueue();
echo 'event: getMessagingQueue' . PHP_EOL;
echo 'data: ' . json_encode( $result) . PHP_EOL;
ob_end_flush();
flush();
usleep($sleepTime * 1000000);
}
The problem here is that I get the following warning/notices
Warning: session_start(): Cannot send session cache limiter - headers already sent and
Notice: ob_end_flush(): failed to delete and flush buffer. No buffer to delete or flush i
The reason that I need the session_start() in the loop is because I will need to unlock the session file immediately after reading it before the script sleeps.
The idea is to
Start/resume session
Update session values
Display the content of the session
Unlock/release the session to allow other processes to use it
Go back to step 1
What can I do to avoid the errors here?
In your specific case... I would do without sessions. A session is just a serialized object into a temporary (well, garbage-collected) file. Who's to stop you from writing your own alternate session handler?
In its most basic form, leaving aside error checking and recovery, it's something like
function getAlternateSessionData() {
$raw = file_get_contents(getAlternateSessionFileName());
return unserialize($raw);
}
and the same with file_put_contents() to save the session. You do have to consider how to guard against concurrent access to the same session file by two different server processes. But you can send the session ID cookie at the beginning only, thus never actually "restarting" the session.
Another possibility would be to implement your alternate session using memory - if the server process does not terminate - or something like Redis (you can do that with ordinary sessions too, but those would send a session cache limiter).
You can also use session_set_save_handler() to override the file reading and writing functions of PHP with functions of your own, leaving all the session handling (cookies, etc.) intact. By handling yourself the session file management, you would be able to commit the session whenever you wanted. Still, you would need to consider carefully how to go with concurrent access to the session files, even if they are compatible with PHP's own session handler (if they aren't, you will have to include your session handler in every script accessing the "new" sessions).
As for ob_end_flush(), I believe that's because you never did call ob_start(). You can check the ob* level using ob_get_level() and flush it securely before the loop using ob_end_clean(), then instate a ob_start()/ob_end_flush() pair inside the loop.
Instead of starting the session for each loop, start it only if it hasn't already been started.
For PHP 5.4 and higher:
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
For PHP 5.3 and lower:
if(session_id() == '') {
session_start();
}
You can't call ob_end_flush() without calling ob_start() first, but You can just use ob_flush() for what you are trying to do.
Lastly, are you doing this in a browser? The browser will give up on the page if it takes to long too respond, and looping indefinitely will eventually cause a time out. You'd be better off using ajax/javascript, or a meta refresh tag instead of looping on the PHP side.

Php, share SESSION between requests, forcing session_id

My goal is to share session between requests, I meant every request could reach a data. First I was thinking that simply sharing via filesystem could be good, but I find out this by myself:
session_id('0');
session_start();
echo session_id();
var_dump ($_SESSION);
if (!isset($_SESSION['x']))
{
$_SESSION['x'] = 0;
}
$_SESSION['x']++;
var_dump ($_SESSION);
this way I can see the same from browsers. My question is, is it a good practice?
EDIT: here is the full working version:
$m = microtime(true);
session_start();
if (session_id() == '0') // this happens when somehow our session id sticks, it should not happen ever, but if so, lets erase it
{
setcookie (session_name(), '', time() - 3600);
session_destroy();
session_write_close();
echo 'reload'; die;
}
if (!isset($_SESSION['x']))
{
$_SESSION['x'] = 0;
}
$_SESSION['x']++;
$saveId = session_id();
session_write_close();
// switch to common storage
session_id('0');
session_start();
if (!isset($_SESSION['common']))
{
$_SESSION['common'] = 0;
}
$_SESSION['common']++;
session_write_close();
// back to our own session
session_id($saveId);
session_start();
echo $_SESSION['x'].'<br>'.(microtime(true) - $m); die;
I dont thing its very time consuming.
It's tricky to know if SESSION is the right place to put this data, but it's worthwhile bearing some things in mind.
SESSION is designed to store data related to an individual user's visit to your site (normally being distinguished the combination of machine and browser, thanks to the session id being stored in a client side cookie).
Default behaviour of the PHP session handler is to:
Store the data in a file on the server.
Block concurrent access to that file.
It is possible to have multiple sessions for a given request, but that means ensuring you start and end each session and ensure that you keep track of the session IDs - I'm not entirely sure how you would do this without manually writing data into the client's cookie.
All in all you'll probably find that your performance using the session will be slower that just checking the existence of the file anyway (which is simpler than using the session, in terms of work done by PHP).
That said, if you're writing to that file then you're just going to have concurrency issues that you'll have to solve in much the same way as php sessions do anyway.
I'd say, if you're writing data, then look to your DB. It's what they're designed for.
If you don't want to write to your primary DB and have good reason for that, then maybe consider something like a memcache DB, or some other secondary storage.

PHP sessions are blocking, ignoring session_write_close

I am trying to serve two near simultaneous requests originating from the same browser session.
Consider the following minimal example:
<?php
session_start();
$sessionId = session_id();
session_write_close();
$file = sys_get_temp_dir() . '/lock_test';
if (!file_exists($file)) {
touch($file);
sleep(5);
unlink($file);
echo 'done! ' . $sessionId;
} else {
echo 'locked! ' . $sessionId;
}
The second request should result in the "locked" output but it always waits for the first request to complete and then outputs "done".
Xdebug is not running. PHP version is 5.5.
Edit:
Voting to close this as a duplicate. The linked question suggests that to get around this issue, append a random variable. So I would suggest appending a requestTime variable and setting it to a timestamp with microseconds.
This behaviour is OK. If both requests share the same session and you are using files to store session data, PHP needs to flock() the session file to prevent it from getting corrupted by multiple, concurring requests which would attempt to write to the file. (Of course PHP will always flock() the session file, but having multiple concurring requests which share the same session, you can see it in effect)

How to kill a/all php sessions?

I have a very basic php session login script. I want to force logout of a certain user or force logout of all users.
How can I read all sessions made to my website, and destroy some or all sessions?
You could try to force PHP to delete all the sessions by doing
ini_set('session.gc_max_lifetime', 0);
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 1);
That forces PHP to treat all sessions as having a 0-second lifetime, and a 100% probability of getting cleaned up.
The drawback is that whichever unlucky user runs this first will get a long pause while PHP does cleanup, especially if there's a lot of session files to go through.
For one particular user, you'd have to add some code to your session handler:
if ($_SESSION['username'] == 'user to delete') {
session_destroy();
}
PHP's garbage collector isn't controllable, so you can't give it parameters such as "delete all sessions except for user X's". It looks strictly at the last-modified/last-accessed timestamps on the session files and compares that to the max_lifetime setting. It doesn't actually process the session data.
You can use session_save_path() to find the path where PHP saves the session files, and then delete them using unlink().
Updated - Aug 2012
This code is based from the official PHP site, and another well written snippet on SO.
<?php
// Finds all server sessions
session_start();
// Stores in Array
$_SESSION = array();
// Swipe via memory
if (ini_get("session.use_cookies")) {
// Prepare and swipe cookies
$params = session_get_cookie_params();
// clear cookies and sessions
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
// Just in case.. swipe these values too
ini_set('session.gc_max_lifetime', 0);
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 1);
// Completely destroy our server sessions..
session_destroy();
?>
Works well. Servers like NGinx you can turn off, clean cache, swipe memory reset, clear logs etc and generally remove temp usage. Even drop the limits of memory.
Clearling all sessions at once would require first knowing which session.save_handler is being used to store sessions and locating the session.save_path in order to delete all sessions. For deleting the current session only, refer to the documentation for session_destroy().
Here are some common examples for deleting all sessions using standard file and memcached save handlers:
Using file save handler
foreach(glob(ini_get("session.save_path") . "/*") as $sessionFile) {
unlink($sessionFile);
}
Using memcached save handler
$memcached = new Memcached;
$memcached->addServers($listOfYourMemcachedSesssionServers);
// Memcached session keys are prefixed with "memc.sess.key." by default
$sessionKeys = preg_grep("#^memc\.sess\.key\.#", $memcached->getAllKeys());
$memcached->deleteMulti($sessionKeys);
Of course, you might want to consider only doing this out of band from your normal HTTP client requests, since cleaning up large session storage may take some time and have inadvertent side effects in a normal request life cycle.
I found this code very helpful and it really worked for me
<?php
$path = session_save_path();
$files = glob($path.'/*'); // get all file names
foreach($files as $file){ // iterate files
if(is_file($file))
unlink($file); // delete file
}
?>
It depends on your session storage.
If you're using PHP session storage, then they may be in the temporary directory of your server. Deleting the selected files will "kill" the session. However if your server is in running state, that session file may be occupied by HTTP process and you won't be able to delete it. Just look at the image below. File named as starting with "+~" are all session files.
A nicer solution is to use a database session storage and delete the selected sessions from there. You can check out HTTP_Session2 which has multiple containers.
I will create a txt file containing the token which has the same value as the generated login session as a comparison every time the user is logged in:
if($_SERVER['REQUEST_METHOD'] == 'POST') {
$token = sha1(uniqid(mt_rand(), true));
if($everything_is_valid) {
// Set login session
$_SESSION[$_POST['username']] = $token;
// Create token file
file_put_contents('log/token.' . $_POST['username'] . '.txt', $token);
// Just to be safe
chmod('log/token.' . $_POST['username'] . '.txt', 0600);
}
}
Checks for logged in user(s):
if(isset($_SESSION['charlie']) && file_exists('log/token.charlie.txt') && $_SESSION['charlie'] == file_get_contents('log/token.charlie.txt')) {
echo 'You are logged in.';
}
So, if you want to force this charlie user to be logged out, simply remove the token file:
// Force logout the `charlie` user
unlink('log/token.charlie.txt');
Taufik's answer is the best i could find.
However, you can further modify it
After authenticating the user and creating the session variables, add these lines:
$token = "/sess_" . session_id();
file_put_contents('log/' . $_SESSION['id'] . '.txt', $token);
If you need to force the user to log out during a cronjob or by an admin request:
$path = session_save_path();
$file = file_get_contents('log/xxx.txt'); // xxx is user's id
$url = $path.$file;
unlink($url);
remove all session variables
session_unset();
destroy the session
session_destroy();

Categories