Logging out must remove the login state completely. A half-done logout leaves a session that still works.
The logout steps
- Clear the session data.
- Expire the session cookie.
- Destroy the session on the server.
- Redirect to the login page.
<?php
declare(strict_types=1);
session_start();
require __DIR__ . "/functions.php"; // csrfCheck()
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
http_response_code(405);
header("Allow: POST");
exit;
}
csrfCheck();
$_SESSION = [];
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), "", [
"expires" => time() - 42000,
"path" => $params["path"],
"domain" => $params["domain"],
"secure" => $params["secure"],
"httponly" => $params["httponly"],
"samesite" => $params["samesite"] ?: "Lax",
]);
}
session_destroy();
header("Location: login.php");
exit;
These are the steps from Destroying a Session.
The logout button
Use a form with POST, not a link.
<form method="post" action="logout.php">
<input type="hidden" name="csrf_token" value="<?= h(csrfToken()) ?>">
<button type="submit">Log out</button>
</form>
If logout worked through a link (logout.php), any page could log your users out by loading it in an image tag. That is a mild annoyance, but an annoyance you can avoid.
If you also set a remember-me cookie
Logging out must also delete the long-lived token from the database and expire its cookie. See Remember Me.
Log out everywhere
Sometimes a user wants to end all their sessions (after a password change, or if a device is lost). To support it, you need a record of sessions per user (a table), or a session version number on the user that you compare on every request.
<?php
function sessionIsCurrent(array $session, array $user): bool
{
return ($session["session_version"] ?? 0) === (int) $user["session_version"];
}
$user = ["id" => 1, "session_version" => 3]; // from the database
var_dump(sessionIsCurrent(["session_version" => 3], $user));
var_dump(sessionIsCurrent(["session_version" => 2], $user)); // an older login
bool(true)
bool(false)
Increase session_version in the database when the user presses “log out everywhere” or changes the password. Every older session then fails the check on its next request.
Automatic logout
Combine a logout with an idle timeout. If the last request was more than, say, 30 minutes ago, destroy the session and ask the user to log in again.
After logout
- Show a short message: “You have been logged out.”
- Make sure the browser does not show private pages from its cache when someone presses the Back button. Pages for logged-in users can send
Cache-Control: no-store.
<?php
header("Cache-Control: no-store");