What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build the login as two separate operations: retrieve one account with a prepared SQL statement, then call password_verify() on the submitted password and stored hash. If both checks succeed, regenerate the session ID and store only the user’s server-side ID in the session. Use HTTPS for the login and every authenticated page, generic failure messages, and protected session cookies before treating the feature as production-ready.

1. Design the user account record

A login needs a stable account record rather than a password copied into a session or compared directly in SQL. A practical users table contains:

  • A unique login identifier, such as a username or verified email address.
  • A password hash, stored as the complete string returned by password_hash().
  • An account-status flag such as is_active.
  • An immutable numeric account ID used by the session.
  • Creation and update timestamps for administration, auditing, and recovery workflows.

Make the password-hash column generously sized. PHP documents that PASSWORD_DEFAULT may change over time and recommends allowing up to 255 bytes, so a VARCHAR(255) column avoids a future schema change.

2. Hash passwords when accounts are created

At registration and every password change, hash the supplied password on the server:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
$hash = password_hash($password, PASSWORD_DEFAULT);

Store the entire returned value. It includes the algorithm, cost, and salt information that PHP needs for later verification. A password hash is a one-way record, not an encrypted password: your application should never be able to recover the original text, and it must not store or log it.

Validate registration input separately from authentication. Enforce your application’s length and account-identifier rules, but do not truncate a password before hashing.

3. Serve the login over HTTPS

Submit the login form to an HTTPS endpoint and serve every authenticated response over HTTPS. TLS protects the password while it travels between the browser and server; hashing the password in the database does not protect a password submitted over an unencrypted connection. Redirecting HTTP to HTTPS is useful, but the login page itself should be loaded over HTTPS so the form and its cookies are not initially exposed.

4. Retrieve the account with a prepared query

Use PDO or mysqli prepared statements. Bind the submitted username or email as a value rather than concatenating request data into SQL. Select only what this decision needs: the account ID, stored hash, and status.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

5. Verify the submitted password

Pass the submitted password and the stored hash to password_verify(). Do not compare plaintext values, manually split the hash, or create a new hash and compare strings. PHP documents that password_verify() returns a Boolean result and is safe against timing attacks.

6. Establish the authenticated session

After the account is active and the password verifies, rotate the session identifier and then save a minimal server-side identity:

session_regenerate_id(true);
$_SESSION['user_id'] = (int) $user['id'];

Regenerating the ID after authentication prevents an attacker from fixing a session ID before the victim logs in. Do not put the password, password hash, or a complete account row in the session. On later requests, read the stored ID and load current account permissions from the database.

7. Complete login endpoint example

The following is an illustrative PDO pattern. Adapt the table, column, validation, CSRF handling, and URL paths to your application:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $login = trim((string) ($_POST['login'] ?? ''));
    $password = (string) ($_POST['password'] ?? '');

    $stmt = $pdo->prepare(
        'SELECT id, password_hash, is_active
         FROM users
         WHERE email = :login
         LIMIT 1'
    );
    $stmt->execute(['login' => $login]);
    $user = $stmt->fetch(PDO::FETCH_ASSOC);

    if ($user
        && (int) $user['is_active'] === 1
        && password_verify($password, $user['password_hash'])) {
        session_regenerate_id(true);
        $_SESSION['user_id'] = (int) $user['id'];
        header('Location: /account.php', true, 303);
        exit;
    }

    $error = 'Login failed; account disabled.';
}

The query and password check are deliberately separate. The database finds a candidate row; PHP’s password API decides whether the submitted secret matches its stored hash.

8. Use one failure response

Return the same externally visible failure message when the identifier is unknown, the password is wrong, or the account is disabled. Revealing “email not found” versus “password incorrect” helps attackers enumerate accounts. The application can record an internal event for monitoring, but logs must exclude passwords and other secrets.

9. Protect the session lifecycle

Cookie settings

Configure the session cookie with Secure, HttpOnly, and an appropriate SameSite value. Secure limits transmission to HTTPS; HttpOnly prevents ordinary JavaScript from reading the cookie; SameSite reduces cross-site request exposure while your application still needs to support its legitimate flows.

Logout and expiry

Logout should invalidate the server-side session, clear the session cookie, and prevent reuse of the old identifier. Set idle and absolute expiry policies that fit the application’s risk. Require the user to authenticate again for sensitive changes such as replacing an email address, changing a password, or managing recovery factors.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Additional request protections

Add CSRF protection to state-changing forms, including login where your threat model requires it. Apply rate limiting or measured lockout controls to repeated attempts, and log security events without logging credentials. Build a complete password-reset flow separately; do not turn a generic login error into an account-recovery mechanism.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

10. Choose the implementation approach

Approach Strengths Trade-offs to assess
Custom PHP session login Direct control and a small deployment footprint; suitable for a small application when the controls above are implemented deliberately. You must design password reset, MFA, session rotation and revocation, CSRF defenses, throttling, logging, and upgrades.
PHP framework authentication Often supplies established middleware, session handling, validation, reset flows, and integration points. Review its defaults, upgrade path, storage model, and migration effort instead of assuming every security decision is correct for your application.
Hosted identity provider Can provide managed recovery, MFA, federation, and operational monitoring. Introduces provider dependency, integration complexity, recurring service considerations, and a migration plan for identities and sessions.
PDO or mysqli Both support prepared statements for the account lookup. Choose one consistently and use its parameter-binding API correctly; the choice does not replace password or session protections.
Username or verified email Either can be a unique login identifier. Email login requires a verification and change process; usernames require an availability and recovery policy.
Cookie session or tokens Cookie sessions fit traditional PHP pages; tokens can suit separate APIs and services. Tokens add issuance, storage, expiry, rotation, revocation, and theft-handling decisions. Do not choose them merely to avoid configuring a secure session.

11. Verify the finished flow

  • A new account stores a hash produced by password_hash(), never the submitted password.
  • The login query is parameterized and returns at most one account.
  • A correct password for an active account rotates the session ID and redirects with a 303 response.
  • Unknown, inactive, and incorrect credentials produce the same outward failure.
  • The session contains only the account identifier needed by subsequent requests.
  • Cookies, logout, expiry, reauthentication, CSRF defenses, throttling, reset, and MFA decisions are implemented for the application’s threat model.
  • HTTP requests cannot expose the login page, credentials, session cookie, or authenticated content.

Frequently Asked Questions

Can I compare a password with the value stored in MySQL?

No. Store the complete hash from password_hash() and pass the submitted password plus that stored value to password_verify(). Hashes include their verification metadata and are not plaintext passwords.

Should I store the email or username in the PHP session?

Prefer the immutable account ID, such as $_SESSION['user_id']. Load current profile and authorization data from the database so changes take effect without trusting stale session fields.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.