Reputation: 63
I am working on a simple project where I have a login system with multiple users where each user will login and save form entries in a file. I am trying to build session timeout so that after 2 minutes of inactivity it can log me out.
login.php
page where I provide my user and password.index.php
page where I have form with two textbox and a button.index.php
page I have Save Data
button which if I click it calls save.php
then it save form entries by overwriting in file.logout
link on my index.php
page which if I click then it will log me out and redirect to login.php
page.All above things works fine. Now I am trying to build this session timeout so that it can log me out after x minutes of inactivity and redirect me to login.php
page.
Here is my index.php
file:
<?php
declare(strict_types = 1);
// Start session.
session_start();
// Include helper functions.
require_once 'helpers.php';
// 2 mins in seconds
$inactive = 120;
if(isset($_SESSION['timeout']) ) {
$session_life = time() - $_SESSION['timeout'];
if($session_life > $inactive)
{
redirect('logout.php');
return;
}
}
$_SESSION['timeout'] = time();
// Redirect user to login page if not authenticated.
if (! check_auth()) {
redirect('login.php');
return;
}
?>
<!doctype html>
<html>
<head>
<title>Home</title>
</head>
<body>
<div>
<h1>Website Title</h1> <a href="logout.php">Logout</a> </div>
<div>
<p>Welcome back, <?= $_SESSION['user_id'] ?>!</p>
</div>
<form method="post">
<input type="text" name="field1" />
<input type="text" name="field2" />
<input type="submit" name="submit" value="Save Data"> </form>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(function() {
$('form').submit(function(e) {
e.preventDefault();
$.post({
url: 'save.php',
data: $(this).serialize(),
}).done(response => {
response = JSON.parse(response);
if(response.message) {
alert(response.message);
}
});
});
});
</script>
</body>
</html>
Here is my save.php
file:
<?php
declare(strict_types=1);
// Start session.
session_start();
// Include helper functions.
require_once 'helpers.php';
// 2 mins in seconds
$inactive = 120;
if(isset($_SESSION['timeout']) ) {
$session_life = time() - $_SESSION['timeout'];
if($session_life > $inactive)
{
redirect('logout.php');
return;
}
}
$_SESSION['timeout'] = time();
// Redirect user to login page if not authenticated.
if (! check_auth()) {
redirect('login.php');
return;
}
// save form entries in a file
Here is my helpers.php
file:
<?php
declare(strict_types=1);
if (! function_exists('check_auth')) {
function check_auth(): bool
{
return isset($_SESSION['user_id']);
}
}
if (! function_exists('logout'))
{
function logout()
{
if (isset($_SESSION['user_id'])) {
$trace = json_decode(file_get_contents('current_user.txt'));
if ((int) $trace->user_id === $_SESSION['user_id']) {
$content = isset($trace->revoked_user_id)
? json_encode(['user_id' => $trace->revoked_user_id, 'created_at' => (new DateTime('now'))->format('Y-m-d H:i:s')])
: '';
file_put_contents('current_user.txt', $content);
}
unset($_SESSION['user_id']);
unset($_SESSION['timeout']);
}
}
}
if (! function_exists('redirect')) {
function redirect(string $url, int $status_code = 303): void
{
header('Location: ' . $url, true, $status_code);
die();
}
}
Problem Statement
I am redirecting to logout.php
file once it is idle for 2 minutes in index.php
and save.php
. Here is what I am noticing -
index.php
page and after 2 minutes of inactivity, if I refresh my browser then it logs me out fine without any issues and redirects me to login.php
page.index.php
page again and after 2 minutes of inactivity if I click on Save Data
button then it gives me error on my console but techincally it should have logged me out and redirected me to login.php page.Below is the ajax call I have in my index.php
page -
<script>
$(function() {
$('form').submit(function(e) {
e.preventDefault();
$.post({
url: 'save.php',
data: $(this).serialize(),
}).done(response => {
response = JSON.parse(response);
if(response.message) {
alert(response.message);
}
});
});
});
</script>
I get a json parsing error for my point 2 whenever I click Save Data
button after 2 minutes of inactivity. And value of response
variable is full login.php
in html. I am not sure why it is happening. Do we need to check for session validation in jquery itself before it calls save.php
?
Upvotes: 1
Views: 697
Reputation: 18416
In summary, your issue is caused by the redirect occurring in save.php
when it is being requested by ajax.
What happens is the redirect()
request is processed transparently and the results are being processed by the jQuery.ajax().done()
closure, which is trying to call JSON.parse(response);
for the HTML of login.php
.
You should be able to validate this by viewing the developer tools (usually F12) in your browser, when clicking the button.
The general approach to resolve the issue, is to determine if the request is a XMLHttpRequest
and send a different response instead of redirecting.
To determine if the request is coming from jQuery.ajax()
, you would check the X-Requested-With
request header.
isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'
redirect
propertyThis approach returns a redirect
property in the response and uses it to redirect from the jQuery.ajax().done()
closure.
helpers.php
if (! function_exists('redirect')) {
function redirect(string $url, int $status_code = 303): void
{
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
//do not redirect AJAX requests
echo json_encode(['redirect' => $url]);
} else {
header('Location: ' . $url, true, $status_code);
}
die();
}
}
index.php
$(function() {
"use strict";
$('form').submit(function(e) {
e.preventDefault();
$.post({
url: 'save.php',
data: $(this).serialize(),
}).done(response => {
response = JSON.parse(response);
if (response.redirect) {
//redirect user
window.location.href = response.redirect;
}
if (response.message) {
alert(response.message);
}
});
});
});
This approach returns a status code other than 200 Ok
, instead of redirecting and checks for the response status code in the jQuery.ajax()
or statusCode:
method(s).
helpers.php
if (! function_exists('redirect')) {
function redirect(string $url, int $status_code = 303): void
{
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
//do not redirect AJAX requests
http_response_code(401); //Unauthorized - PHP 5.4+
echo $url; //optionally output the url
} else {
header('Location: ' . $url, true, $status_code);
}
die();
}
}
Here you can choose to handle the status code as desired.
For the simplest approach, checking the fail()
jqXhr object status code and perform a javascript redirect instead.
index.php
$(function() {
"use strict";
$('form').submit(function(e) {
e.preventDefault();
$.post({
url: 'save.php',
data: $(this).serialize(),
}).done(response => {
response = JSON.parse(response);
if (response.message) {
alert(response.message);
}
}).fail(jqXhr => {
if (jqXhr.status == 401) {
//redirect to specified url in the response text
window.location.href = jqXhr.responseText;
/* alternatively hard-code '/logout.php' */
}
});
});
});
Upvotes: 2