Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save bigprof/3d607b23227b7e421d4a7322cf1d7d78 to your computer and use it in GitHub Desktop.
Save bigprof/3d607b23227b7e421d4a7322cf1d7d78 to your computer and use it in GitHub Desktop.
Session Inactivity Timeout Implementation in AppGini Applications

Session Inactivity Timeout Implementation in AppGini Applications

Overview

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:

Session timeout system workflow overview diagram

Architecture

The session timeout system consists of three main components:

  1. Server-side session management (hooks/__bootstrap.php)
  2. Client-side inactivity detection (hooks/footer-extras.php)
  3. Global session checking (hooks/__global.php)

The diagram below illustrates how these components interact to provide a seamless user experience:

Session timeout architecture diagram

Server-Side Implementation

Configuration (hooks/__bootstrap.php)

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.

Session Options

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.

Session Activity Monitoring

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');
}

Full code example for hooks/__bootstrap.php

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');
}

Client-Side Implementation

Inactivity Detection (hooks/footer-extras.php)

The client-side implementation provides:

  1. User activity monitoring: Tracks mouse movements, keyboard input, and touch events
  2. Visual warnings: Shows countdown notifications before logout
  3. Automatic logout: Redirects to logout page when timeout is reached
  4. Session keep-alive: Sends AJAX requests to maintain server-side sessions

Key Features

Activity Events Monitored

  • mousemove: Mouse movement
  • keydown: Keyboard input
  • mousedown: Mouse clicks
  • touchstart: Touch screen interactions

Warning System

  • Displays a fixed-position warning dialog 60 seconds before logout
  • Shows real-time countdown of remaining seconds
  • Automatically removes warning when user becomes active

Session timeout warning notification in browser

Code for hooks/footer-extras.php

<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 as SESSION_LIFETIME defined above in hooks/__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.

Global Integration

Automatic Session Checking (hooks/__global.php)

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.

Custom Page Integration

Automatic Behavior for Custom Pages

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'); ?>

Benefits for Custom Pages

  1. No additional configuration required
  2. Consistent user experience across all application pages
  3. Automatic session management without custom implementation
  4. Visual timeout warnings maintain user awareness

Configuration Options

Adjusting Timeout Duration

To modify the session timeout, update the SESSION_LIFETIME constant in hooks/__bootstrap.php:

define('SESSION_LIFETIME', 1800); // 30 minutes

Customizing Warning Behavior

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.

Technical Details

URLs and Endpoints

  • Logout URL: index.php?signOut=1
  • Keep-alive endpoint: ajax_check_login.php?keep-alive=1

Session Management Flow

  1. User loads any AppGini page
  2. checkSessionActivity() validates session on server
  3. Client-side timer starts monitoring activity
  4. User activity resets both client and server timers
  5. Warning appears 60 seconds before timeout
  6. Automatic logout occurs if no activity

Session management flow diagram

Keep-Alive Mechanism

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:

Security Considerations

  1. Server-side enforcement: Session validation occurs on the server, preventing client-side bypass
  2. AJAX protection: Keep-alive requests are authenticated and validated
  3. Graceful degradation: System works even if JavaScript is disabled (server-side only)
  4. Guest user handling: Inactive timer only applies to authenticated users

Troubleshooting

Common Issues

  1. Sessions expiring too quickly: Check SESSION_LIFETIME configuration
  2. Warning not appearing: Verify .username element exists on the page
  3. Keep-alive not working: Check browser console for AJAX errors
  4. Inconsistent behavior: Ensure checkSessionActivity() is called in hooks/__global.php
  5. 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? :)

Browser console debugging information for session timeout

Debugging

  • 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()

Response headers showing session activity timestamp

Conclusion

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment