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