First of all, I kind of learned this wrong initially. I always thought that PHP sessions were something unique and secure intrinsically. But sessions are just cookies that terminate at the end of the browser session. PHP sessions are more intelligently crafted than your average cookie, but they've got most of the same vulnerabilities and flaws. Cookies just plain aren't safe.
First of all, you should set session.use_only_cookies
. Oftentimes it's easier to get an HTTP request than it is to get cookies from an unsuspecting victim. History is easier to read, and people often clear cookies without clearing history. It's also displayed on your freaking monitor, so you might as well have your URL be http://example.com/admin/index.php?username=admin&password=MyPaSsWoRd!123
You have two possible courses of action: if you use session_regenerate_id(true)
, you'll need to implement a system for multiple simultaneous logons -- or you can just assume that if somebody logs in from another device, everybody else's access will terminate.
I prefer an inactivity timeout, because a session lasts as long as the browser is open -- and who knows how long that will be?
You just need this logic:
On login:
<?php
$_SESSION['Admin'] = true;
$_SESSION['timeout'] = time()+3600; // +1 hour
?>
And on subsequent admin pages:
<?php
if (!isset($_SESSION['timeout']) || $_SESSION['timeout'] < time()) {
// destroy the session
// redirect to login
exit;
}
?>
(If you're developing a UI, maybe you want to add a warning when the timeout approaches so that the user doesn't lose his information.)
Don't worry about putting everything on index.php
, because none of that matters.
Don't rely on things like the client's OS, browser, or IP address. These are unreliable, subject to change, and can be easily spoofed. Although the IP address can't be faked (technically, not to any use), your IP address might change during the session and you could lock yourself out.
To be honest, if you're going to have multiple sign-ins from different devices at the same time, I'd avoid regenerating the session ID. If you're the only user, then use it, because it does sort of add a layer of security. The scenario is, if a session ID is obtained, the hacker only has a short window of opportunity to use the session before it is regenerated by you, the user.
Charles Miller wrote a great article about persistent authentication, and this is where I'll point you to, because this is how I learned how to do my auth: http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice/ (Disregard the Barry Jaspan link at the bottom, it's not really any more secure, and it's needlessly more complicated.) session_regenerate_id
would make implementing this system pretty easy -- and it'd be very simple if you only had one user. In fact, you could store a history of session IDs and, assuming that no session ID will ever be reused in the foreseeable future of your app (and it's highly unlikely), you could simply say that if someone tries to access your app using a previously used and now invalidated session ID, you can assume they're a hacker and act accordingly.
Here's a way to implement just that (using no database, for simplicity's sake):
<?php
$sessions = explode(PHP_EOL, file_get_contents('sessions.log'));
// Get the history of all past sessions
if (in_array(session_id(), $sessions) {
// The current session ID is a session that has already been discarded
// Hijack alert!
mail('youremail@example.com', 'HIJACK ALERT!', 'Somebody tried to use the session '.session_id().'! The bastard\'s IP was '.$_SERVER['REMOTE_ADDR'], 'From: php@server');
session_destroy();
echo 'Nice try, jerk! The admin has been notified....';
exit;
}
if (isset($_SESSION['Admin']) && $_SESSION['Admin']) {
// Logged in!
file_put_contents('sessions.log', PHP_EOL.session_id(), FILE_APPEND);
// Add the current session ID to a file containing a history of session IDs
session_regenerate_id(true);
// Regenerate the session and delete the old one
}
?>
(Untested.) Granted, this is a rather primitive example, but it might be what you need.
The best solution is, as I mentioned in the comments, HTTP authentication, so use that if it's an option.
And the last thing we talked about: hashing the password. If somebody gains access to your file server and consequently your source code, you're in trouble and your application is compromised regardless. But if you use the same password elsewhere, by all means, only store a hash of it in the source code so you can limit the damage. Then, if your password is the same as your bank account's password, at least the hacker only sees
if (hash('whirlpool', $_POST['password'].$salt) == '9923afaec3a86f865bb231a588f453f84e8151a2deb4109aebc6de4284be5bebcff4fab82a7e51d920237340a043736e9d13bab196006dcca0fe65314d68eab9')
(P.S. Whirlpool is the best hash, in my opinion. And my favorite way to salt is to split the password in half and stick the salt in the middle. Then, even if both are known and the hacker has a super hash table, he's still screwed. e.g. $half = floor(strlen($password)/2); $hash = hash('whirlpool', substr($password, 0, $half).$salt.substr($password, $half));
)