first php projects

Login Authentication Flow

Store Password Hashes

Create a users table with email, password_hash, and a role or explicit admin flag. Generate the initial hash from a setup script:

PHP example
<?php

declare(strict_types=1);

echo password_hash('change-this-password', PASSWORD_DEFAULT) . PHP_EOL;

Store the resulting hash, never the plaintext password.

Verify Credentials Without Leaking Accounts

PHP example
<?php

declare(strict_types=1);

$authenticated = $user !== null
    && password_verify($submittedPassword, $user['password_hash']);

if (!$authenticated) {
    throw new RuntimeException('Invalid email or password.');
}

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

Use the same public error for an unknown email and a wrong password. Record failures safely and add rate limiting so repeated guesses become slower or blocked.

Protect Every Admin Route

Create one guard such as requireAdmin(). Call it from product create, edit, and delete handlers before changing data. Hiding navigation links is useful presentation, but it is not authorization.

Logout should clear authenticated session state and invalidate the session cookie according to the session configuration used by the app.

Verify The Session Lifecycle

Check valid credentials, wrong password, unknown email, anonymous product edits, non-admin edits, logout, and repeated failures.

Practice

Practice: Build A Session Login Flow

Implement a password-and-session administrator login for the product manager.

Requirements

  • Store password hashes created by password_hash().
  • Verify submitted passwords with password_verify().
  • Regenerate the session ID after successful login.
  • Invalidate authenticated session state on logout.
  • Do not store plaintext passwords.
  • Do not rely on hidden navigation links for access control.
  • Use secure cookie settings under HTTPS.

Protect every create, edit, and delete handler server-side. Verify that logout removes access and repeated failed attempts reach the chosen rate-limit path.

Show solution

Generate one development password hash and store it in a seeded user row. Normalize the email for lookup, verify the password with password_verify(), regenerate the session ID on success, and store the user ID in the session.

Use one server-side guard for every administrator action:

PHP example
<?php

function requireAdmin(?array $currentUser): void
{
    if ($currentUser === null || $currentUser['role'] !== 'admin') {
        http_response_code(403);
        exit('Forbidden');
    }
}

Return the same public login failure for unknown users and wrong passwords. Verify session regeneration, rejected anonymous edits, rejected non-admin edits, logout, and rate-limited failures.