Master Secure Authentication: Your Guide to PHP Login

In this extensive guide, we will comprehensively explain the process of creating a login form using PHP, specifically focusing on username and password inputs. By gaining more knowledge on this topic, you’ll be poised to significantly enhance your web development skills.

Login Form Fundamentals

First and foremost, it’s important to grasp the fundamentals. A login form is a critical aspect of almost every website where user interaction is required. It provides a mechanism for users to identify themselves and gain access to specific resources, such as personal profiles or administrator panels.

The two key values that a user inputs in a standard login form are the username and password. The username is commonly an email address or a custom alias, while the password is a secret key that only the user should know.

Initiate the creation of the login.php page within the public directory as your first step.

Next, proceed to delineate a login form encompassing the requisite elements: username and password input fields, accompanied by a conspicuously placed login button.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://www.phptutorial.net/app/css/style.css">
    <title>Login</title>
</head>
<body>
<main>
    <form action="login.php" method="post">
        <h1>Login</h1>
        <div>
            <label for="username">Username:</label>
            <input type="text" name="username" id="username">
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" name="password" id="password">
        </div>
        <section>
            <button type="submit">Login</button>
            <a href="register.php">Register</a>
        </section>
    </form>
</main>
</body>
</html>

Similar to the approach employed for the register.php page, you have the liberty to recycle the header.php and footer.php files sourced from the src/inc directory. Implement the view() function to seamlessly integrate them into the login.php page, as depicted below:

<?php

require __DIR__ . '/../src/bootstrap.php';
?>

<?php view('header', ['title' => 'Login']) ?>
<main>
    <form action="login.php" method="post">
        <h1>Login</h1>
        <div>
            <label for="username">Username:</label>
            <input type="text" name="username" id="username">
        </div>

        <div>
            <label for="password">Password:</label>
            <input type="password" name="password" id="password">
        </div>

        <section>
            <button type="submit">Login</button>
            <a href="register.php">Register</a>
        </section>
    </form>
</main>
<?php view('footer') ?>

The login form, upon submission, directs the data flow to login.php. Consequently, it is prudent to verify the HTTP request method as POST prior to engaging in form processing.

In order to facilitate form processing, you shall be tasked with the creation of a login.php file within the src directory.

<?php

$inputs = [];
$errors = [];

if (is_post_request()) {

    [$inputs, $errors] = filter($_POST, [
        'username' => 'string | required',
        'password' => 'string | required'
    ]);

    if ($errors) {
        redirect_with('login.php', ['errors' => $errors, 'inputs' => $inputs]);
    }

    // In the event of a login failure
    if (!login($inputs['username'], $inputs['password'])) {

        $errors['login'] = 'Invalid username or password';

        redirect_with('login.php', [
            'errors' => $errors,
            'inputs' => $inputs
        ]);
    }
    // Login executed successfully
    redirect_to('index.php');

} else if (is_get_request()) {
    [$errors, $inputs] = session_flash('errors', 'inputs');
}

To elucidate the modus operandi:

Primarily, initialize two variables designated to house sanitized data and error messages:

$inputs = [];
$errors = [];

Subsequently, ascertain whether the HTTP request method is in fact a POST request by invoking the is_post_request() function:

if (is_post_request()) {
    // ...
}

Thirdly, employ the filter() function to sanitize and validate user inputs:

[$inputs, $errors] = filter($_POST, [
    'username' => 'string | required',
    'password' => 'string | required'
]);

Fourthly, in instances where either the username or password is absent, employ the post-redirect-get (PRG) methodology to redirect users back to the login.php page while setting the $errors and $inputs within the session using the redirect_with() function:

if ($errors) {
    redirect_with('login.php', [
        'errors' => $errors,
        'inputs' => $inputs
    ]);
}

Fifthly, invoke the login() function to authenticate the provided username and password. In the event that neither matches, append an error message under the ‘login’ key and steer users back to the login.php page:

<?php

if (!login($inputs['username'], $inputs['password'])) {

    $errors['login'] = 'Invalid username or password';

    redirect_with('login.php', [
        'errors' => $errors,
        'inputs' => $inputs
    ]);
}

Seventhly, when both the username and password correspond successfully, direct users towards the index.php page:

redirect_to('index.php');

The index.php page, incidentally, serves as a password-protected enclave, allowing exclusive access solely to authenticated users. More detailed information on this aspect shall be provided in the subsequent section.

Finally, in cases where the HTTP request method is GET, retrieve the $inputs and $errors from the session:

[$errors, $inputs] = session_flash('errors', 'inputs');

For the purpose of rendering the login.php page, certain modifications are necessary to accommodate the display of user inputs and any accompanying error messages:

<?php

require __DIR__ . '/../src/bootstrap.php';
require __DIR__ . '/../src/login.php';
?>

<?php view('header', ['title' => 'Login']) ?>

<?php if (isset($errors['login'])) : ?>
    <div class="alert alert-error">
        <?= $errors['login'] ?>
    </div>
<?php endif ?>

<form action="login.php" method="post">
    <h1>Login</h1>
    <div>
        <label for="username">Username:</label>
        <input type="text" name="username" id="username" value="<?= $inputs['username'] ?? '' ?>">
        <small><?= $errors['username'] ?? '' ?></small>
    </div>
    <div>
        <label for="password">Password:</label>
        <input type="password" name="password" id="password">
        <small><?= $errors['password'] ?? '' ?></small>
    </div>
    <section>
        <button type="submit">Login</button>
        <a href="register.php">Register</a>
    </section>
</form>

<?php view('footer') ?>

It should be noted that the login() function, crucial for the authentication process, necessitates definition.

Elucidate the login() function

The login() function accepts a username and password as inputs and bestows veracity upon them if they prove valid. The operational logic of the login() function is delineated as follows:

  • Initial, embark on a quest to unearth the username within the repository of users. Should the user entity manifest itself, proceed to the subsequent step;
  • Subsequently, engage in a rigorous scrutiny to ascertain the congruence of the submitted password;
  • Tertially, should the username remain an enigma within the annals of the system or should the password fail to harmonize, precipitate the issuance of an error proclamation.

To ascertain the presence of a user in the expanse of users, you have the prerogative to define a function titled find_user_by_username() nestled within the src/auth.php file:

function find_user_by_username(string $username)
{
$sql = 'SELECT username, password
FROM users
WHERE username=:username';

$statement = db()->prepare($sql);
$statement->bindValue(':username', $username, PDO::PARAM_STR);
$statement->execute();

return $statement->fetch(PDO::FETCH_ASSOC);

}

Code language: PHP (php)

In the event that a username finds sanctuary within the sanctum of users, the find_user_by_username() function shall proffer an associative array replete with twin elements, bearing the keys of username and password. Contrariwise, it shall dispense a falsehood.

Given that the citadel of data harbors the password’s cryptographic hash, the necessity arises to enlist the services of the built-in password_verify() function to juxtapose the unadorned password with its cryptographic counterpart.

The password_verify() function renders a verdict of veracity if the plaintext password aligns with its cryptographic sibling, or conversely, a proclamation of falsehood.

In the event of concordance between both username and password, the sanctified act of user admittance can transpire. To effectuate this, you must imbue the $_SESSION with a definable attribute; for instance:

$_SESSION['username'] = $username;
Code language: PHP (php)

In subsequent requests, the $_SESSION variable becomes the compass by which the existence of a user, identified by their username, is ascertained. To illustrate:

// Ascertaining the presence of a logged-in username
if(isset($_SESSION['username'])) {
// The status quo indicates an established login
// ...
}
// Ascertaining the presence of a logged-in username
if(isset($_SESSION['username'])) {
// The status quo indicates an established login
// ...
}

Code language: PHP (php)

Herewith, is presented the comprehensive login() function in all its grandeur:

function login(string $username, string $password): bool
{
$user = find_user_by_username($username);

// In the presence of a found user, authentication of the password commences
if ($user && password_verify($password, $user['password'])) {

    // Forestall the malevolent stratagem of session fixation
    session_regenerate_id();

    // Inscribing the username within the sanctum of the session
    $_SESSION['username'] = $user['username'];
    $_SESSION['user_id']  = $user['id'];


    return true;
}

return false;

}
Code language: PHP (php)

It warrants notice that upon the initiation of user login, it is prudent to summon the session_regenerate_id() function to bestow upon the session a fresh and invigorated identity. This measure acts as a bulwark against the nefarious designs of session fixation.

The subsequent section introduces the is_user_logged_in() function enshrined within the precincts of src/auth.php, a function that ascertains the veracity of a user’s active login status:

function is_user_logged_in(): bool
{
return isset($_SESSION['username']);
}
Code language: PHP (php)

For those who traverse the digital realm without the imprimatur of login, redirection to the hallowed precincts of login.php awaits. Within these confines, the require_login() function stands sentinel, poised to enforce this stringent decree:

function require_login(): void
{
if (!is_user_logged_in()) {
redirect_to('login.php');
}
}
Code language: PHP (php)

The clarion call to action is to invoke the require_login() function at the threshold of any page that mandates the imprimatur of login.

For example, within the public/index.php page, you may herald this decree thusly:

<?php

require __DIR__ . '/../src/bootstrap.php';
require_login();
?>
Program code on a computer screen

The PHP Logout Process

A basic yet essential feature of any user authenticated website is the ability for users to log out. This function disengages user sessions, ensuring privacy and security. 

Understanding the Logout Functionality

When a user logs in, the PHP session is initiated, storing the user’s credentials. In the logout process, PHP destroys this session and redirects the user back to the login page.

In an earlier tutorial, we discussed the login() function, which stored the username and user_id into the $_SESSION variable. To log out, we simply need to remove these details.

Building the Logout() Function

The function named logout() is defined in the auth.php file. This is a crucial function that helps manage user sessions:

function logout(): void
{
    if (is_user_logged_in()) {
        unset($_SESSION['username'], $_SESSION['user_id']);
        session_destroy();
        redirect_to('login.php');
    }
}

This function begins with a check to see if a user is logged in. If true, the unset function removes ‘username’ and ‘user_id’ from the $_SESSION variable. The session_destroy()function is then called to terminate the session, and the user is redirected back to the login page using redirect_to(‘login.php’).

Accessing Current User Details

In many cases, it’s necessary to fetch the details of the currently logged-in user. The current_user() function can help you do just that:

function current_user()
{
    if (is_user_logged_in()) {
        return $_SESSION['username'];
    }
    return null;
}

This function returns the current user’s details if a user is logged in. Otherwise, it returns null.

Adding a Logout Option

Upon successful login, users are directed to the index.php file. A welcome message is displayed on this page along with a logout option.

<?php
require __DIR__ . '/../src/bootstrap.php';
require_login();
?>

<?php view('header', ['title' => 'Dashboard']) ?>
<p>Welcome <?= current_user() ?> <a href="logout.php">Logout</a></p>
<?php view('footer') ?>

The logout option is a link that redirects users to the logout.php page.

Implementing the Logout Feature

To enable user logout, we need to call the logout() function when users click on the logout link. This is accomplished by creating a logout.php page in the public directory:

<?php
require __DIR__ . '/../src/bootstrap.php';
logout();
?>

User Redirection for Pre-Logged-In Individuals

In the event that users find themselves already logged into their accounts and navigate to either the login.php or register.php pages, a course of action is required to redirect them seamlessly to the index.php page.

To effectuate this process, it is incumbent upon you to incorporate the ensuing code snippet at the commencement of both the login.php and register.php files, residing within the src directory:

<?php

if (the user is currently authenticated) {
    carry out a seamless redirection to 'index.php';
}
Code language: HTML, XML (xml)

Below, you shall observe the login.php script within the src directory:

<?php

if (the user is currently authenticated) {
    carry out a seamless redirection to 'index.php';
}

An array labeled ‘$inputs’ shall be initialized, alongside an array designated as ‘$errors’.

In cases where an HTTP POST request is detected, the ensuing procedures are executed:

The user inputs are subjected to sanitization and validation, encompassing the ‘username’ and ‘password’ fields, which are mandated and ought to be of string data type.

If validation errors are encountered during this process, a redirection is triggered to the ‘login.php’ page, wherein both the ‘errors’ and ‘inputs’ arrays are furnished as part of the redirection payload.

Should the login attempt prove unsuccessful, resulting from an invalid username or password, the ‘login’ field within the ‘errors’ array is populated with the message ‘Invalid username or password’. Subsequently, another redirection to ‘login.php’ is executed, including the ‘errors’ and ‘inputs’ arrays within the redirection payload.

In the event of a successful login, an immediate redirection is carried out, guiding the user to the ‘index.php’ page.

Alternatively, in cases of an HTTP GET request, the ‘errors’ and ‘inputs’ arrays are populated via the retrieval of session flash data.

Please be mindful to retain the subject and variable names ‘is_user_logged_in’, ‘is_post_request’, ‘login’, ‘redirect_to’, ‘redirect_with’, and ‘session_flash’ as indicated in the original content.

Conclusion

Creating a secure, efficient PHP login form that uses a username and password for authentication is a vital and valuable skill for any web developer. This comprehensive guide aimed to walk you through each step of the process; from understanding the basics of login forms to sophisticated topics like data sanitation and secure authentication. Remember, keeping your users’ data secure should always be the top priority.