October 25, 2024
Chicago 12, Melborne City, USA
HTML

Issue with Session Persistence and Login Redirect in PHP Application


I have implemented session handling and a login form to restrict access to certain pages like the dashboard. The session logic and login form are handled in common.php with the code below

<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Login</title>
    </head>
    <body>

    <?php
    $timeout_duration = 60; // 60 seconds timeout
    ini_set('session.gc_maxlifetime', $timeout_duration);
    session_set_cookie_params([
        'lifetime' => $timeout_duration,
        'path' => '/',
        'secure' => isset($_SERVER['HTTPS']),
        'httponly' => true,
        'samesite' => 'Strict'
    ]);

    define('SESSION_TIMEOUT', 1800); // 30 minutes in seconds

    // 2. Improved session configuration
    function initializeSession() {
        // Set session cookie parameters
        $secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
        $params = session_get_cookie_params();
        session_set_cookie_params([
            'lifetime' => 0, // Session cookie (expires when browser closes)
            'path' => '/',
            'domain' => $_SERVER['HTTP_HOST'],
            'secure' => $secure,
            'httponly' => true,
            'samesite' => 'Lax'  // Allow sessions across tabs while maintaining security
        ]);

        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        // Initialize session if needed
        if (!isset($_SESSION['created'])) {
            $_SESSION['created'] = time();
            $_SESSION['last_activity'] = time();
        }
    }

    // 3. Improved session validation
    function validateSession() {
        if (!isset($_SESSION['user_session'])) {
            return false;
        }

        // Check if session has expired
        if (isset($_SESSION['last_activity']) && 
            (time() - $_SESSION['last_activity'] > SESSION_TIMEOUT)) {
            destroySession();
            return false;
        }

        // Update session timing
        $_SESSION['last_activity'] = time();

        // Regenerate session ID periodically (every 10 minutes)
        if (!isset($_SESSION['created']) || (time() - $_SESSION['created'] > 600)) {
            session_regenerate_id(true);
            $_SESSION['created'] = time();
        }

        return true;
    }

    // Skip session start and session handling if the page is in a whitelisted iframe
    if (!isIframeWhitelisted()) {
        session_start();
    }

    // Include database connection
    require_once 'db_connect.php';

    // Function to check if session is expired
    function isSessionExpired() {
        global $timeout_duration;
        if (!isset($_SESSION['last_activity'])) {
            return true;
        }
        return (time() - $_SESSION['last_activity']) > $timeout_duration;
    }

    // Function to regenerate session ID and update last activity
    function regenerateSession() {
        // Check if session is active before regenerating session ID
        if (session_status() === PHP_SESSION_ACTIVE) {
            $_SESSION['last_activity'] = time();
            session_regenerate_id(true);
        }
    }

    // Proper session destruction, including removing session cookie
    function destroySession() {
        session_unset();
        session_destroy();
        
        if (ini_get("session.use_cookies")) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', time() - 42000,
                $params["path"], $params["domain"],
                $params["secure"], $params["httponly"],
                $params["samesite"] ?? ''
            );
        }
    }

    // Handle iframe whitelist for automatic login
    function isIframeWhitelisted() {
        $whitelistedDomains = ['domain1.com', 'domain2.com'];
        $referer = isset($_SERVER['HTTP_REFERER']) ? parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) : '';
        return in_array($referer, $whitelistedDomains);
    }

    // Function to authenticate user from the database
    function authenticateUser($email, $password, $conn) {
        $query = "SELECT password FROM `dc-users-passwords` WHERE email = :email";
        $stmt = $conn->prepare($query);
        $stmt->bindParam(':email', $email, PDO::PARAM_STR);
        $stmt->execute();
        
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        
        if ($result && hash_equals($result['password'], sha1($password))) {
            return true;
        }else{

        }
        return false;
    }

    // Main function to handle login and session
    function secureLogin() {
        global $conn;

        // Handle iframe whitelist for automatic login
        if (isIframeWhitelisted()) {
            $_SESSION['iframe_session'] = bin2hex(random_bytes(32));
            regenerateSession();
            return true;
        }

        // Handle POST login request
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
            $password = $_POST['password'];

            if (authenticateUser($email, $password, $conn)) {
                $_SESSION['user_session'] = bin2hex(random_bytes(32));
                regenerateSession();
                return true;
            } else {
                displayLoginForm('Incorrect email or password. Please try again.');
                return false;
            }
        }

        // Validate existing session for inactivity, but skip this check if inside an iframe
        if (isset($_SESSION['user_session'])) {
            if (isIframeWhitelisted()) {
                // If in iframe, just regenerate session without checking for expiration
                regenerateSession();
                return true;
            } elseif (!isSessionExpired()) {
                regenerateSession();
                return true;
            } else {
                destroySession();
            }
        }

        return false;
    }

    // Function to display login form
    function displayLoginForm($error="") {
        $errorMessage = $error ? "<p style="color: red;">$error</p>" : '';
        echo '<label class="loginLabelClass" for="email">Email</label>
            <input class="emailFieldInput" type="text" id="email" name="email" placeholder="Email" required>
            <label class="passwordLabel" for="password">Enter your password</label>
            <input class="passwordFieldInput" type="password" name="password" placeholder="Password" required>';
        exit;
    }

    // Main authentication logic
    $authenticated = secureLogin();

    if (!$authenticated) {
        displayLoginForm();
    }

    // Reset session timer, only if not in an iframe
    if (!isIframeWhitelisted()) {
        $_SESSION['last_activity'] = time();
    }
    ?>




       
    <script>
    const sessionMonitor = {
        checkInterval: 15000, // Check every 15 seconds
        timeoutDuration: 70000, // 70 seconds for demo purposes
        lastActivity: Date.now(),
        sessionExpired: false, // Flag to stop further checks after session expiration
        
        init: function() {
            // Check if inside iframe
            if (window.self !== window.top) {
                console.log("Inside iframe, skipping session checks.");
                return; // Skip session monitoring if in iframe
            }

            console.log("Not in iframe, starting session monitor.");
            
            // Set up activity listeners
            ['mousedown', 'keydown', 'scroll', 'touchstart'].forEach(eventType => {
                document.addEventListener(eventType, () => this.updateActivity());
            });
            
            // Start the check interval
            this.startChecking();
        },
        
        updateActivity: function() {
            if (!this.sessionExpired) {
                this.lastActivity = Date.now();
            }
        },
        
        startChecking: function() {
            this.checkIntervalID = setInterval(() => this.checkSession(), this.checkInterval);
        },
        
        checkSession: function() {
            // Check if user has been inactive
            if (Date.now() - this.lastActivity >= this.timeoutDuration && !this.sessionExpired) {
                fetch('check_session.php')
                    .then(response => response.json())
                    .then(data => {
                        console.log(data.status);
                        if (data.status === 'expired') {
                            this.sessionExpired = true; // Set the flag to stop further checks
                            clearInterval(this.checkIntervalID); // Stop the interval to prevent further refreshes
                            window.location.href = window.location.href; // Refresh the page
                        }
                    })
                    .catch(error => console.error('Session check failed:', error));
            }
        }
    };

    // Initialize the session monitor when the page loads
    document.addEventListener('DOMContentLoaded', () => sessionMonitor.init());
    </script>
    </body>
    </html>

And this is included in dashboard.php as:

<?php
require_once("common.php");
?>

When I log in, it successfully redirects me to the dashboard, and I can view the page content as expected. However, I am facing an issue when manually entering or refreshing the URL in the browser’s address bar. If I directly access the dashboard.php URL (e.g., by typing it in the address bar or refreshing the page), the session seems to be lost, and I am redirected back to the login form.
It appears that the session is not persisting across page reloads or direct URL access. I’m not sure what I’m missing in the session handling logic, and I would appreciate any guidance on how to fix this so that the session remains active when accessing the URL directly.

Here is the check_session.php

<?php
// check_session.php
session_start();
$timeout_duration = 3600; // Make sure this matches the value in common.php

header('Content-Type: application/json');

$response = array(
    'status' => '',
    'message' => ''
);

if (!isset($_SESSION['last_activity']) || (time() - $_SESSION['last_activity']) > $timeout_duration) {
    $response['status'] = 'expired';
    $response['message'] = 'Session has expired';
    session_unset();
    session_destroy();
} else {
    $response['status'] = 'active';
    $response['message'] = 'Session is active';
    $_SESSION['last_activity'] = time();
}

echo json_encode($response);
exit;
?>



You need to sign in to view this answers

Leave feedback about this

  • Quality
  • Price
  • Service

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video