The AppGini Blog
A few tips and tricks to make your coding life a tiny bit better.

Advanced Tutorial: Implementing Session Timeout in Your AppGini Application

Introduction

This tutorial guides advanced AppGini users through implementing a robust session inactivity timeout system in their own applications. You’ll learn how to combine server-side session management with client-side user experience enhancements, ensuring both security and usability for your users.


1. Why Session Timeout?

Session timeouts help protect your application by automatically logging out inactive users, reducing the risk of unauthorized access. This tutorial covers both the technical and user experience aspects of session timeout.


2. Server-Side: Enforcing Session Expiry

Note: For a more in-depth understanding of the code, refer to the Session Inactivity Timeout Implementation in AppGini Applications gist on GitHub. This gist provides a detailed breakdown of the session timeout implementation, including explanations of each part of the code and how it integrates with AppGini’s architecture.

a. Set the Session Lifetime

In your hooks/__bootstrap.php, define how long a session should last (in seconds):

1
define('SESSION_LIFETIME', 300); // 5 minutes

Related: Contents of the generated hooks folder > __bootstrap.php

b. Configure PHP Session Parameters

Still in __bootstrap.php, ensure session garbage collection and cookie lifetime match your timeout:

1
2
3
4
function session_options(&$options) {
    $options['gc_maxlifetime'] = SESSION_LIFETIME;
    $options['cookie_lifetime'] = SESSION_LIFETIME;
}

c. Enforce Timeout on Every Request

Add the checkSessionActivity() function to log out users who exceed the timeout. Also place this function in hooks/__bootstrap.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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();

    // Optional: Add a header for debugging
    @header('X-Last-Session-Activity: ' . (time() - ($_SESSION['last_activity'] ?? 0)) . ' seconds ago');
}

Here is the full code to include in your hooks/__bootstrap.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
define('SESSION_LIFETIME', 300); // 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();

    // Optional: Add a header for debugging
    @header('X-Last-Session-Activity: ' . (time() - ($_SESSION['last_activity'] ?? 0)) . ' seconds ago');
}

d. Call Session Check Globally

In your hooks/__global.php, call this function at the top so it runs on every page:

1
checkSessionActivity();

Related: AppGini global hooks


3. Client-Side: User Experience & Warnings

a. Add Inactivity Timer Script

In your hooks/footer-extras.php, add a script to:

  • Monitor user activity (mouse, keyboard, touch)
  • Show a warning before logout
  • Log out the user after inactivity
  • Send a keep-alive AJAX request if the user becomes active in the last minute

Related: Contents of the generated hooks folder > footer-extras.php

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<script>
setTimeout(() => {
    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, warningTimer, warningInterval;

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

        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() {
        if (inactivityTimer) {
            const timeElapsed = inactivityTimeoutSeconds * 1000 - (Date.now() - resetInactivityTimer.lastReset);
            if (timeElapsed <= 60000) {
                fetch(resetUrl, { method: 'GET' }).catch(() => {});
            }
        }
        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>

b. How It Works

  • User activity resets the timer.
  • Warning appears 60 seconds before logout.
  • Keep-alive AJAX request is sent if activity occurs in the last minute.
  • Logout happens if no activity.

The screenshot below shows the warning notification that appears in the bottom-right corner of the browser window, displaying a countdown timer indicating how many seconds remain before the user is logged out due to inactivity.

Session timeout warning notification in browser

The video below demonstrates the network tab showing keep-alive AJAX requests sent to ajax_check_login.php with the keep-alive parameter. This happens when the user is active within the last minute before session timeout, ensuring the session remains valid.


4. Custom Pages: Automatic Coverage

Any custom page that includes the standard AppGini header and footer will inherit this session timeout system. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
    define('PREPEND_PATH', '../');
    define('APP_ROOT', __DIR__ . '/' . PREPEND_PATH);

    include(APP_ROOT . 'lib.php');
    include_once(APP_ROOT . 'header.php');

    // ... your page content and logic here ...

    include_once(APP_ROOT . 'footer.php');

No extra code is needed for custom pages!


5. Configuration & Customization

  • Change timeout: Edit SESSION_LIFETIME in __bootstrap.php.
  • Change warning time: Adjust the 60 in the warning logic in footer-extras.php.
  • Change warning style: Edit the HTML/CSS in the warning div in footer-extras.php.

6. Troubleshooting & Tips

  • If sessions expire too quickly, check your PHP session settings and SESSION_LIFETIME.

  • If the warning doesn’t appear, make sure .username exists in your page markup (that you haven’t removed it via other customizations).

  • Use browser dev tools to monitor AJAX keep-alive requests.

  • The system works even if JavaScript is disabled, but users won’t see warnings.

  • Remember me won’t work with this system. If you want inactive users to be forced to log in again, it then makes sense that a user coming back after a few days or weeks will have to log in again, even if they had previously checked the “Remember me” option. You can remove the “Remember me” checkbox from the login form by adding the following code to your hooks/footer-extras.php inside the <script> tag:

    1
    2
    3
    
    $j(() => {
        $j('#rememberMe').closest('.checkbox').remove();
    });
    

Browser console debugging information for session timeout


Note: For a more in-depth understanding of the code, refer to the Session Inactivity Timeout Implementation in AppGini Applications gist on GitHub. This gist provides a detailed breakdown of the session timeout implementation, including explanations of each part of the code and how it integrates with AppGini’s architecture.

Conclusion

With this approach, you get a secure, user-friendly session timeout system that works across your entire AppGini app—including custom pages. Advanced users can further customize the logic, UI, or integration as needed.