Preparing the Database and Environment
Before any code runs, a solid foundation in the database and server configuration is essential. Modern PHP runs best on version 8 or later, but the concepts below apply to PHP 5.6 and up. The first step is choosing the data store: MySQL, MariaDB, or PostgreSQL are the most common options. Each of these systems supports robust encryption functions, but the login script itself will keep the passwords and tokens hashed in the database, so the specific engine matters less than its ability to enforce unique constraints and support binary columns.
The core table you need holds five pieces of information per user: an auto‑incrementing primary key, a unique username, a hashed password, a remember‑me token, the current session id, and the IP address that last authenticated. A simple schema looks like this:
In this design the password and cookie columns store 64‑character hashes. The original example used MD5, but that algorithm is no longer considered safe for password storage. Replace it with password_hash() (bcrypt or Argon2) and password_verify() when hashing and checking passwords. For the remember‑me token, a random string generated with bin2hex(random_bytes(32)) offers stronger entropy than MD5.
Connecting to the database should use a library that handles errors gracefully. PEAR DB works, but PDO is more common in recent projects. A minimal wrapper that throws exceptions on failure keeps the rest of the script clean:
Session handling must be configured before any output. Set session.save_path to a directory owned by the web server that is not world‑writable. Using a custom session handler or a database‑backed session store can add another layer of security, especially on shared hosts. After configuring the save path, always call session_start() at the beginning of each request.
Protect the session cookie itself. In PHP you can set session.cookie_secure = 1 and session.cookie_httponly = 1 so the cookie is only transmitted over HTTPS and not accessible to JavaScript. Adding session.cookie_samesite = 'Strict' reduces CSRF risk. When setting a custom cookie for the remember‑me feature, repeat these flags.
When a new user signs up, generate a password hash and a random remember‑me token. Store them alongside the username. The script will later verify them against the database. Avoid storing plain text or reversible values. The design above keeps the session id in the database as well, enabling the script to detect session hijacking by comparing the id and the IP address. This simple check will catch many attacks on shared hosts or when /tmp is used for session files.
In short, set up a clean database schema, use modern hashing functions, configure sessions properly, and enforce secure cookie attributes. These steps give the login script a robust platform to operate safely.
Implementing the Secure Login Workflow
The heart of the authentication logic lives in a small object that encapsulates all session‑related operations. The class constructor examines whether a user is already logged in, either through an existing session or a remember‑me cookie. If a session exists it calls _checkSession(); if not, it looks for a cookie and runs _checkRemembered(). This separation keeps the request flow clear and the class methods focused.
Before any method runs, session_defaults() guarantees that the $_SESSION superglobal contains a predictable set of keys. This function sets logged to false, clears the user id and username, and flags that the user is not remembered. By invoking it early, the script avoids undefined index notices and simplifies later checks.
When a login form posts a username, password, and an optional “remember” checkbox, the method _checkLogin() receives those values. It first escapes the username with a prepared statement placeholder and hashes the password using password_hash() for storage. The query looks up a row that matches the username and the hashed password. If a match is found, _setSession() is called with the user record and the remember flag. On failure, the script flags failed, triggers a logout, and returns false. This binary outcome lets the caller decide whether to display an error message or redirect.
The _setSession() method populates the session array: it stores the user id, the username sanitized with htmlspecialchars() to mitigate XSS, the cookie token, and sets logged to true. If the user opted to be remembered, updateCookie() writes a secure cookie that contains the username and token, serialized as a single string. When the session is first established, _setSession() updates the database with the current session id and IP address. This step uses session_id() and $_SERVER['REMOTE_ADDR']. The database record then serves as a guard against session hijacking: future requests must match these two values or the user is logged out.
The remember‑me feature relies on _checkRemembered(). It receives the cookie value, unserializes it to retrieve the stored username and token, and validates them against the database. If the pair matches, the user is automatically logged in by calling _setSession() with the remember flag set to true. Because the cookie never contains a password, an attacker who steals the cookie still cannot impersonate the user without the matching database record.
Session integrity is enforced by _checkSession(). Whenever a request arrives with a valid $_SESSION['logged'], this method pulls the username, cookie token, session id, and IP from the session array, quotes them, and queries the database for an exact match. If the record exists, the session remains active. If not, _logout() clears the session variables and redirects the user to a login page. This process protects against session fixation and hijacking because any mismatch forces a fresh authentication.
Logging out is simple: _logout() calls session_defaults(), destroys the session, and, if a remember‑me cookie is present, clears it by setting a past expiration date. A clean logout ensures that no residual state can be exploited later.
Security best practices go beyond these methods. First, always serve the application over HTTPS; this prevents man‑in‑the‑middle attacks from reading or tampering with cookies. Second, regenerate the session id after a successful login with session_regenerate_id(true) to mitigate fixation. Third, use CSRF tokens on any form that modifies user data. Fourth, store passwords with password_hash() and verify with password_verify(); avoid MD5 or plain hashing. Fifth, prepare all SQL statements to defend against injection. These layers together make the authentication process robust against common web threats.
In practice, using the class is straightforward. A typical request starts with:
If a login form was submitted, call:
With this pattern, the login logic stays isolated from the rest of the application, making maintenance easier and the code more readable. The design encourages future enhancements: moving the database layer to an ORM, adding multi‑factor authentication, or logging audit trails for each authentication event. Adopting these improvements further hardens the system while keeping the core flow familiar.





No comments yet. Be the first to comment!