Block concurrent login: reject new session if account already active
If session_token is set and session_last_seen is within the last hour, the incoming login is rejected with a clear message. Stale sessions (idle > 1 h) and explicit logouts (token = NULL via back.php) still allow re-login normally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
9a50238347
commit
fda211b1a8
@@ -23,9 +23,10 @@
|
|||||||
* b. The elapsed time since otpTime is ≤ 5 minutes.
|
* b. The elapsed time since otpTime is ≤ 5 minutes.
|
||||||
* Fail either → return "Wrong OTP! Please try again."
|
* Fail either → return "Wrong OTP! Please try again."
|
||||||
* 5. On success:
|
* 5. On success:
|
||||||
* a. Force-replace session_token in DB — writing a new token invalidates
|
* a. Concurrent-session check — if the account already has a session_token
|
||||||
* any prior session (old device gets kicked out by db_auth.php on its
|
* set and session_last_seen is within the last hour, the login is blocked
|
||||||
* next request). No block: password + OTP is full 2FA proof of identity.
|
* with "already signed in on another device." A stale or NULL token allows
|
||||||
|
* the login (user closed browser without logging out, or used back.php).
|
||||||
* b. session_regenerate_id(true) — prevents session fixation attack by
|
* b. session_regenerate_id(true) — prevents session fixation attack by
|
||||||
* issuing a new session ID and deleting the old one.
|
* issuing a new session ID and deleting the old one.
|
||||||
* c. Generate a fresh CSRF token and store in session.
|
* c. Generate a fresh CSRF token and store in session.
|
||||||
@@ -106,13 +107,30 @@ if (empty($_SESSION['skip_otp'])) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step 4b: Claim session — always replace existing token ────────────────────
|
// ── Step 4b: Concurrent session check ────────────────────────────────────────
|
||||||
// Password + OTP is full 2FA proof of identity, so we always grant the login.
|
// Block the login if this account already has an active session.
|
||||||
// Writing a new token here also invalidates any prior session: db_auth.php
|
// "Active" = session_token is set AND session_last_seen is within the last hour.
|
||||||
// compares session_token in the DB to the one stored in the PHP session, so the
|
// A stale last_seen (user closed browser without logging out) expires after 1 h,
|
||||||
// old device is kicked out on its next request. This also fixes the case where
|
// matching the PHP session GC maxlifetime configured in session.php.
|
||||||
// a PHP session expired without clearing the DB token, which would otherwise
|
// An explicit logout clears session_token to NULL, so back.php bypasses this.
|
||||||
// permanently block re-login.
|
$sth_active = $pdo1->prepare(
|
||||||
|
"SELECT session_token, session_last_seen FROM user WHERE user_id = :uid LIMIT 1"
|
||||||
|
);
|
||||||
|
$sth_active->execute([':uid' => $user_id]);
|
||||||
|
$active_row = $sth_active->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!empty($active_row['session_token']) && !empty($active_row['session_last_seen'])) {
|
||||||
|
$idle_seconds = time() - strtotime($active_row['session_last_seen']);
|
||||||
|
if ($idle_seconds < 3600) {
|
||||||
|
$answer['message'] = 'This account is currently signed in on another device. Please sign out from that session first.';
|
||||||
|
exit(json_encode($answer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Step 4c: Claim session ────────────────────────────────────────────────────
|
||||||
|
// No active session found (or it has gone stale) — write a new token.
|
||||||
|
// db_auth.php compares session_token in the DB to the one in the PHP session,
|
||||||
|
// so any tab that still holds the old token is invalidated on its next request.
|
||||||
$session_token = bin2hex(random_bytes(32));
|
$session_token = bin2hex(random_bytes(32));
|
||||||
$sth_claim = $pdo1->prepare(
|
$sth_claim = $pdo1->prepare(
|
||||||
"UPDATE user
|
"UPDATE user
|
||||||
|
|||||||
Reference in New Issue
Block a user