Back to Blog
Lesson 46 of the PHP: Modern PHP from the Ground Up course
PHPSeptember 3, 20263 min read

Building a Simple Authentication System: Secure PHP Basics

Master authentication in PHP by implementing secure password hashing and session management to protect your application's routes effectively.

PHPauthenticationpassword hashingsession managementsecurity
Close-up of a computer screen displaying an authentication failed message.

Previously in this course, we discussed Database Transactions to ensure our data remains consistent. In this lesson, we build on that foundation by implementing a secure authentication system, focusing on password hashing and session management to control access to your application.

The Security Mindset: Hashing vs. Encryption

The cardinal rule of authentication is simple: never store passwords in plain text. If your database is compromised, a plain-text password list is a disaster for your users.

Instead, we use password hashing. Hashing is a one-way transformation: you convert a password into a unique string of characters. You cannot "decrypt" a hash to reveal the original password. When a user logs in, you re-hash their input and compare it to the stored hash.

In modern PHP, we use password_hash() and password_verify(). These functions automatically handle "salting"—adding random data to the password before hashing to prevent rainbow table attacks.

Implementing Authentication Logic

To integrate this into our MVC project, we need two core methods in our User model.

PHP
class User {
    #6A9955">// Hash a password before saving to the DB
    public function register(string $username, string $password): bool {
        $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
        
        $stmt = $this->db->prepare("INSERT INTO users(username, password) VALUES(?, ?)");
        return $stmt->execute([$username, $hashedPassword]);
    }

    #6A9955">// Verify a login attempt
    public function authenticate(string $username, string $password): ?array {
        $stmt = $this->db->prepare("SELECT * FROM users WHERE username = ?");
        $stmt->execute([$username]);
        $user = $stmt->fetch();

        if ($user && password_verify($password, $user['password'])) {
            return $user;
        }
        return null;
    }
}

Protecting Routes with Session Management

Once a user is verified, we must persist their state. As discussed in Managing State with Superglobals, we use sessions to store the user's ID after a successful login.

To protect a route, you simply check if the user identity exists in the session. Here is how you might implement this in a Controller:

PHP
class DashboardController {
    public function index() {
        session_start();
        
        if (!isset($_SESSION['user_id'])) {
            header('Location: /login');
            exit;
        }
        
        #6A9955">// Render the dashboard view
        require 'views/dashboard.php';
    }
}

Comparison: Authentication vs. Authorization

While authentication confirms who the user is, authorization confirms what they can do.

ConceptPurposeImplementation
AuthenticationVerifies identityLogin forms, password hashing
AuthorizationChecks permissionsRole checks, access control lists

Hands-on Exercise: The Login Gate

  1. Update your User model to include the authenticate method shown above.
  2. Create a LoginController that handles the POST request from a login form.
  3. If authenticate returns a user array, store the user_id in $_SESSION.
  4. Create a protected controller method that checks $_SESSION['user_id'] and redirects guests to the login page.

Common Pitfalls

  • Weak Hashing Algorithms: Never use md5() or sha1() for passwords. They are cryptographically broken and fast to crack. Always use PASSWORD_DEFAULT.
  • Forgetting exit: When redirecting with header('Location: ...'), always follow it with exit. Otherwise, the remainder of your script will continue to execute, potentially revealing protected information.
  • Case Sensitivity: Ensure your password comparison is strict. password_verify is designed for this, but if you manually compare strings, use hash_equals() to prevent timing attacks.

FAQ

Why does password_hash return a different string every time? It includes a unique salt in the output string. This ensures that even if two users have the same password, their stored hashes will look different.

How do I handle "Remember Me" functionality? That requires persistent cookies, which are more complex than standard session management. Stick to standard sessions until you are comfortable with secure token storage.

Can I store user roles in the session? Yes, once you verify the password, you can store the user's role (e.g., 'admin') in $_SESSION['role'] to simplify permission checks later.

Recap

We've implemented a secure authentication flow: hashing passwords with password_hash(), verifying them with password_verify(), and locking down routes by checking $_SESSION state. These tools are the foundation of user security in your MVC application.

Up next: We will explore how to manage time and dates effectively using the DateTime class.

Similar Posts