GPTT Developer Blog

← Back to Home

Secure PHP Login System with Sessions: A Production-Level Guide

📖 1,027 Words

Authentication is one of the most important parts of a PHP application. A login system should not only verify usernames and passwords, but should also protect user credentials, database queries, sessions, and form submissions from common security threats.

In this guide, we will build the foundation of a secure PHP login system using password hashing, prepared statements, session security, CSRF protection, and basic authentication best practices.

What you will learn: How to structure a more secure PHP authentication system using modern PHP security practices.

1. Never Store Plain-Text Passwords

Passwords should never be stored directly in the database. If the database is compromised, plain-text passwords can expose user accounts immediately.

PHP provides password_hash() for securely hashing passwords.

$passwordHash = password_hash(
    $password,
    PASSWORD_DEFAULT
);

The generated hash can then be stored in the database instead of the original password.

2. Verify Passwords Correctly

During login, do not compare the submitted password directly with the database value. Use password_verify() to verify the submitted password against the stored hash.

if (password_verify($password, $hashedPassword)) {
    echo "Login successful";
} else {
    echo "Invalid email or password";
}

3. Use Prepared Statements

SQL queries should not directly concatenate user input. Doing so can expose the application to SQL injection attacks.

Instead, use prepared statements with MySQLi or PDO.

$stmt = $conn->prepare(
    "SELECT id, email, password FROM users WHERE email = ?"
);

$stmt->bind_param("s", $email);

$stmt->execute();

$result = $stmt->get_result();

$user = $result->fetch_assoc();

The user-provided email is treated as a parameter rather than being directly inserted into the SQL statement.

4. Build the Login Process

A basic login process can follow this sequence:

Login Form
    ↓
Receive Email and Password
    ↓
Validate Input
    ↓
Query User with Prepared Statement
    ↓
Retrieve Password Hash
    ↓
password_verify()
    ↓
Create Secure Session
    ↓
Redirect to Dashboard

5. Start the PHP Session

Sessions allow PHP applications to remember that a user has successfully authenticated.

session_start();

After successful authentication, store only the information needed by the application inside the session.

$_SESSION['user_id'] = $user['id'];
$_SESSION['email'] = $user['email'];

6. Regenerate the Session ID After Login

After successful authentication, regenerate the session ID. This helps protect the application against session fixation.

session_regenerate_id(true);

The session should be regenerated immediately after the user's credentials have been successfully verified.

7. Protect Dashboard Pages

Pages that require authentication should verify that a valid session exists before displaying protected content.

session_start();

if (!isset($_SESSION['user_id'])) {
    header("Location: login.php");
    exit;
}

This prevents unauthenticated users from directly accessing protected pages.

8. Add CSRF Protection

Authentication forms and other state-changing forms can also be protected using a CSRF token.

Generate a random token and store it in the user's session.

$_SESSION['csrf_token'] = bin2hex(
    random_bytes(32)
);

Include the token inside the form:

<input
    type="hidden"
    name="csrf_token"
    value="<?php echo htmlspecialchars(
        $_SESSION['csrf_token']
    ); ?>"
>

9. Validate the CSRF Token

The submitted token should be compared with the token stored in the session before processing the form.

if (
    !isset($_POST['csrf_token']) ||
    !hash_equals(
        $_SESSION['csrf_token'],
        $_POST['csrf_token']
    )
) {
    die("Invalid CSRF token.");
}

Using hash_equals() provides a safer comparison for security-sensitive values.

10. Validate User Input

User input should be validated before it is processed. For example, email addresses can be checked using PHP's built-in validation tools.

$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    die("Invalid email address.");
}

Validation helps prevent malformed data from entering the application.

11. Avoid Revealing Sensitive Login Information

Login error messages should not reveal whether a specific email address exists in the database.

Instead of displaying different messages such as Email does not exist or Incorrect password, use a generic message.

echo "Invalid email or password.";

12. Logout Securely

A logout process should remove the authenticated session data.

session_start();

$_SESSION = [];

if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();

    setcookie(
        session_name(),
        '',
        time() - 42000,
        $params['path'],
        $params['domain'],
        $params['secure'],
        $params['httponly']
    );
}

session_destroy();

header("Location: login.php");
exit;

13. Recommended Database Structure

A basic users table can contain the user's identifier, email address, password hash, and account timestamps.

users
│
├── id
├── email
├── password
├── created_at
└── updated_at

The password column should contain the generated password hash rather than the user's original password.

14. Protect the Database Connection

Database credentials should not be exposed directly in publicly accessible application files or committed to public repositories.

Keep database configuration separate from the main authentication logic and restrict access to configuration files.

15. Use HTTPS

Login credentials and authenticated sessions should be transmitted over HTTPS. This helps protect sensitive information while it travels between the browser and the server.

Production authentication systems should not rely on unencrypted HTTP connections.

16. Additional Security Measures

  • Use HTTPS for production applications
  • Use password hashing instead of plain-text passwords
  • Use prepared statements for database queries
  • Regenerate session IDs after authentication
  • Protect state-changing forms with CSRF tokens
  • Validate user input
  • Use generic authentication error messages
  • Limit repeated login attempts
  • Log suspicious authentication activity
  • Keep PHP and server software updated

17. Complete Authentication Architecture

User
  ↓
Login Form
  ↓
Input Validation
  ↓
CSRF Validation
  ↓
Prepared SQL Query
  ↓
Retrieve User
  ↓
password_verify()
  ↓
session_regenerate_id()
  ↓
Create Session
  ↓
Protected Dashboard

18. Common PHP Authentication Mistakes

  • Storing passwords as plain text
  • Using MD5 or SHA1 for password storage
  • Building SQL queries with string concatenation
  • Not regenerating the session ID after login
  • Not protecting forms against CSRF
  • Exposing database credentials
  • Using HTTP instead of HTTPS
  • Revealing whether an account exists
  • Allowing unlimited login attempts

19. Production Authentication Checklist

  • Password hashing implemented
  • password_verify() used during login
  • Prepared statements enabled
  • Session ID regenerated after authentication
  • Protected pages check authentication state
  • CSRF tokens implemented
  • User input validated
  • HTTPS enabled
  • Logout implemented
  • Login attempts monitored or rate-limited

Conclusion

A secure PHP login system requires more than simply checking a username and password. Password hashing, prepared statements, session protection, CSRF validation, input validation, HTTPS, and proper logout handling should work together as layers of protection.

The goal is not only to make authentication functional, but to design the authentication flow so that common security weaknesses are addressed before the application reaches production.

Key takeaway: Secure authentication is a layered process. Protect the password, protect the database query, protect the session, protect the form, and protect the communication channel.

Article Statistics
📖 1,027 Words
Back to Top