This document describes a comprehensive session inactivity timeout system for AppGini applications that combines server-side session management with client-side user interface features. The implementation automatically logs out inactive users and provides visual warnings before logout occurs.
The following flow diagram illustrates the complete session timeout system workflow, including user activity detection, warning display, and automatic logout process:
The session timeout system consists of three main components:
- Server-side session management (
hooks/__bootstrap.php
) - Client-side inactivity detection (
hooks/footer-extras.php
) - Global session checking (
hooks/__global.php
)
The diagram below illustrates how these components interact to provide a seamless user experience:
The server-side configuration is centralized in the hooks/__bootstrap.php
file. We define a constant for the session lifetime, which determines how long a user can remain inactive before being logged out:
define('SESSION_LIFETIME', 300); // in seconds (5 minutes)
Note: If this file does not exist, you can create it in the
hooks
folder of your AppGini application.
The session_options()
function configures PHP session parameters:
function session_options(&$options) {
$options['gc_maxlifetime'] = SESSION_LIFETIME;
$options['cookie_lifetime'] = SESSION_LIFETIME;
}
This function is an AppGini hook that sets the various session options before the session starts. See Modifying Session Behavior via session_options()
Function in __bootstrap.php
for more details.
The checkSessionActivity()
function handles server-side session validation:
- Automatic logout: Users are logged out if their last activity exceeds
SESSION_LIFETIME
- Activity tracking: Updates
$_SESSION['last_activity']
on non-AJAX requests - Keep-alive support: Updates activity time for AJAX requests with
keep-alive
parameter - Response headers: Sets
X-Last-Session-Activity
header for debugging
function checkSessionActivity() {
if(Authentication::isGuest()) return;
if(isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > SESSION_LIFETIME)) {
Authentication::logOut();
if(!is_ajax()) {
redirect('index.php?signOut=1');
exit;
}
}
// Update last activity time unless this is an AJAX request, except if 'keep-alive' is set
if(!is_ajax() || Request::has('keep-alive')) $_SESSION['last_activity'] = time();
// Set a custom header to indicate the last session activity time
@header('X-Last-Session-Activity: ' . (time() - ($_SESSION['last_activity'] ?? 0)) . ' seconds ago');
}
Here's the complete code for the hooks/__bootstrap.php
file that includes session management and activity checking:
<?php
define('SESSION_LIFETIME', 300); // in seconds (5 minutes)
function session_options(&$options) {
$options['gc_maxlifetime'] = SESSION_LIFETIME;
$options['cookie_lifetime'] = SESSION_LIFETIME;
}
function checkSessionActivity() {
if(Authentication::isGuest()) return;
if(isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > SESSION_LIFETIME)) {
Authentication::logOut();
if(!is_ajax()) {
redirect('index.php?signOut=1');
exit;
}
}
// Update last activity time unless this is an AJAX request, except if 'keep-alive' is set
if(!is_ajax() || Request::has('keep-alive')) $_SESSION['last_activity'] = time();
// Set a custom header to indicate the last session activity time
@header('X-Last-Session-Activity: ' . (time() - ($_SESSION['last_activity'] ?? 0)) . ' seconds ago');
}
The client-side implementation provides:
- User activity monitoring: Tracks mouse movements, keyboard input, and touch events
- Visual warnings: Shows countdown notifications before logout
- Automatic logout: Redirects to logout page when timeout is reached
- Session keep-alive: Sends AJAX requests to maintain server-side sessions
mousemove
: Mouse movementkeydown
: Keyboard inputmousedown
: Mouse clickstouchstart
: Touch screen interactions
- Displays a fixed-position warning dialog 60 seconds before logout
- Shows real-time countdown of remaining seconds
- Automatically removes warning when user becomes active
<script>
setTimeout(() => {
// if .username is not present, that is, the user is not signed in, skip this script
if (!document.querySelector('.username')) {
return;
}
const inactivityTimeoutSeconds = <?php echo SESSION_LIFETIME; ?>;
const logoutUrl = <?php echo json_encode(application_url('index.php?signOut=1')); ?>;
const resetUrl = <?php echo json_encode(application_url('ajax_check_login.php?keep-alive=1')); ?>;
let inactivityTimer;
let warningTimer;
let warningInterval;
console.log(`Inactivity timeout set to ${inactivityTimeoutSeconds} seconds.`);
function showWarning() {
let secondsLeft = 60;
if (!document.getElementById('inactivity-warning')) {
document.body.insertAdjacentHTML('beforeend',
`<div id="inactivity-warning" class="alert alert-warning" style="position:fixed; bottom:30px; right:30px; z-index:9999;">
You will be logged out in <span id="inactivity-seconds">${secondsLeft}</span> seconds due to inactivity.
</div>`
);
}
// Update countdown every second
warningInterval = setInterval(() => {
secondsLeft--;
const span = document.getElementById('inactivity-seconds');
if (span) span.textContent = secondsLeft;
if (secondsLeft <= 0) {
clearInterval(warningInterval);
}
}, 1000);
}
function removeWarning() {
const div = document.getElementById('inactivity-warning');
if (div) div.remove();
clearInterval(warningInterval);
}
function resetInactivityTimer() {
// Check if we're close to timeout (within 1 minute) and send keep-alive request
if (inactivityTimer) {
const timeElapsed = inactivityTimeoutSeconds * 1000 - (Date.now() - resetInactivityTimer.lastReset);
if (timeElapsed <= 60000) { // 60 seconds or less remaining
fetch(resetUrl, { method: 'GET' }).catch(err => {
console.warn('Failed to send keep-alive request:', err);
});
}
}
clearTimeout(inactivityTimer);
clearTimeout(warningTimer);
removeWarning();
resetInactivityTimer.lastReset = Date.now();
inactivityTimer = setTimeout(() => {
window.location = logoutUrl;
}, inactivityTimeoutSeconds * 1000);
if (inactivityTimeoutSeconds > 60) {
warningTimer = setTimeout(showWarning, (inactivityTimeoutSeconds - 60) * 1000);
}
}
['mousemove', 'keydown', 'mousedown', 'touchstart'].forEach(evt =>
document.addEventListener(evt, resetInactivityTimer)
);
resetInactivityTimer();
}, 1000);
</script>
This script is included in the footer of every AppGini page, ensuring that all user interactions are monitored and session timeouts are handled consistently.
Here is a breakdown of the key parts of the script:
- Checks if user is signed in by looking for the
.username
element. If no user is signed in, there is no need to run the inactivity timeout script. - Configures several constants:
inactivityTimeoutSeconds
: The session timeout duration in seconds, set to the same value asSESSION_LIFETIME
defined above inhooks/__bootstrap.php
.logoutUrl
: The URL to redirect to when the user is logged out due to inactivity.resetUrl
: The URL for sending keep-alive requests to the server to prevent session expiration.
- Initializes timers for inactivity and warning notifications.
- Monitors user activity through various events (
mousemove
,keydown
,mousedown
,touchstart
). If any activity is detected,resetInactivityTimer()
is called to reset the inactivity timer and remove any existing warning notifications. resetInactivityTimer()
is then called at the beginning to start the inactivity timer. This function:- Checks if the user is close to the timeout threshold (within 60 seconds) and sends a keep-alive request to the server.
- Resets the inactivity timer and warning timer.
- Displays a warning notification 60 seconds before logout, showing a countdown of remaining seconds.
- The entire script runs after a 1-second delay to ensure the DOM is fully loaded before attaching event listeners and starting timers.
The global hooks file ensures session checking occurs on every page load by adding this line:
checkSessionActivity(); // Check session activity to log out inactive users
This call is executed automatically for all AppGini pages, ensuring consistent session management across the application.
Any custom page that includes the standard AppGini header and footer will automatically inherit the session timeout behavior. For example, example1.php
:
<?php
define('PREPEND_PATH', '../');
define('APP_ROOT', __DIR__ . '/' . PREPEND_PATH);
include(APP_ROOT . 'lib.php');
include_once(APP_ROOT . 'header.php'); // Includes footer-extras.php
/* grant access to all logged users */
if(Authentication::isGuest()) die("Access denied");
?>
<div class="page-header">
<h1>Example custom page that respects the session timeout</h1>
</div>
<!-- Custom content here -->
<?php include_once(APP_ROOT . 'footer.php'); ?>
- No additional configuration required
- Consistent user experience across all application pages
- Automatic session management without custom implementation
- Visual timeout warnings maintain user awareness
To modify the session timeout, update the SESSION_LIFETIME
constant in hooks/__bootstrap.php
:
define('SESSION_LIFETIME', 1800); // 30 minutes
The warning appears 60 seconds before logout. To modify this:
if (inactivityTimeoutSeconds > 60) {
warningTimer = setTimeout(showWarning, (inactivityTimeoutSeconds - 60) * 1000);
}
Change 60
to your desired warning duration in seconds.
- Logout URL:
index.php?signOut=1
- Keep-alive endpoint:
ajax_check_login.php?keep-alive=1
- User loads any AppGini page
checkSessionActivity()
validates session on server- Client-side timer starts monitoring activity
- User activity resets both client and server timers
- Warning appears 60 seconds before timeout
- Automatic logout occurs if no activity
The keep-alive feature prevents session expiration when users are active near the timeout threshold:
- Triggered when activity occurs within 60 seconds of timeout
- Sends GET request to
ajax_check_login.php?keep-alive=1
- Server updates
$_SESSION['last_activity']
timestamp - Prevents unnecessary logouts for active users
The video below demonstrates the network activity during keep-alive requests:
- Server-side enforcement: Session validation occurs on the server, preventing client-side bypass
- AJAX protection: Keep-alive requests are authenticated and validated
- Graceful degradation: System works even if JavaScript is disabled (server-side only)
- Guest user handling: Inactive timer only applies to authenticated users
- Sessions expiring too quickly: Check
SESSION_LIFETIME
configuration - Warning not appearing: Verify
.username
element exists on the page - Keep-alive not working: Check browser console for AJAX errors
- Inconsistent behavior: Ensure
checkSessionActivity()
is called inhooks/__global.php
- Remember me not working: That's expected behavior when implementing inactivity timeout. If you don't want user session to persist if user is inactive for a few minutes, it's natural that a user coming back after a few days or weeks will have to sign in, right? :)
- Check
X-Last-Session-Activity
response header for session timing information - Monitor browser console for inactivity timer logs
- Verify session configuration with
session_get_cookie_params()
This session inactivity timeout implementation provides a robust, user-friendly solution for AppGini applications. It combines server-side security with client-side usability, ensuring sessions are properly managed while maintaining a good user experience through visual feedback and intelligent session keep-alive mechanisms.
The system automatically applies to all pages that include the standard AppGini header and footer, making it easy to implement across entire applications without additional configuration for custom pages.