PHP Login

Summary: in this tutorial, you’ll learn how to create a login form using a username and password.

Prerequisites

To start this tutorial, you need to complete the previous tutorial that creates a registration form.

Introduction to the PHP login form

In the previous tutorial, you learned how to create a form that allows users to register for accounts. Once the users register successfully, you redirect them to the login page so that they can use their usernames and passwords to log in.

The login page will contain a form that consists of the username and password inputs and a login button as follows:

PHP Login

To log in, users need to enter their username and password and click the login button.

If the username and password match, you can redirect them to a password-protected page. Otherwise, you redirect the users back to the login page with an error message:

Note that it’s more secure to issue a generic message (invalid username or password) when either username or password doesn’t match.

Create the login form

First, create the login.php page in the public folder.

Second, define a login form with the username and password inputs and a 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>Code language: HTML, XML (xml)

Like the register.php page, you can reuse the header.php and footer.php files from the src/inc folder and use the view() function to load them to the login.php page as follows:

<?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') ?>Code language: PHP (php)

The login form submits to login.php. Therefore, you can check if the HTTP request method is POST before processing the form.

To process the form, you create the login.php in the src folder.

<?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]);
    }

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

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

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

} else if (is_get_request()) {
    [$errors, $inputs] = session_flash('errors', 'inputs');
}Code language: PHP (php)

How it works.

First, define two variables to store the sanitized data and error messages:

$inputs = [];
$errors = [];Code language: PHP (php)

Second, check if the HTTP request method is POST using the is_post_request() function:

if (is_post_request()) {
    // ...
}Code language: PHP (php)

Third, sanitize and validate user inputs using the filter() function:

[$inputs, $errors] = filter($_POST, [
    'username' => 'string | required',
    'password' => 'string | required'
]);Code language: PHP (php)

Fourth, if either username or password is not provided, redirect users to the login.php page using the post-redirect-get (PRG) technique and set the $errors and $inputs in the session using the redirect_with() function.

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

Fifth, call the login() function to verify the username and password.

If either username or password doesn’t match, set an error message with the key login and redirect 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
    ]);
}Code language: PHP (php)

Seventh, if both username and password match, redirect users to the index.php page:

redirect_to('index.php');Code language: PHP (php)

The index.php is the password-protected page. It means that only logged-in users can access it—more on this in the next section.

Finally, get the $inputs and $errors from the session if the HTTP request method is GET.

[$errors, $inputs] = session_flash('errors', 'inputs');Code language: PHP (php)

Show the entered data & error message on the login.php page

The public/login.php needs to change to the following to show the user inputs as well as 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') ?>Code language: PHP (php)

Since the login() function doesn’t exist, we need to define it.

Define the login() function

The login() function accepts username and password and returns true if they are valid. The logic of the login() function is as follows:

  • First, find the username in the users table. If the user exists, go to step 2.
  • Second, verify if the password matches.
  • Third, if the username doesn’t exist or the password doesn’t match, issue an error message.

To find a user from the users table by username, you can define a function find_user_by_username() in 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)

If a username exists in the users table, the find_user_by_username() function returns an associative array with two elements whose keys are username and password. Otherwise, it returns false.

Since you store the password hash in the database, you need to use the built-in password_verify() function to match the plain password with a hash.

The password_verify() function returns true if a plain text password matches a hash, or false otherwise.

If both username and password match, you can log the user in. To do that, you need to set a value in the $_SESSION for example:

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

In subsequent requests, you can check the $_SESSION variable to see if the user with a username is currently logged in. For example:

// check if the username has been logged in
if(isset($_SESSION['username'])) {
    // already logged in
    // ...
}Code language: PHP (php)

Here’s the complete login() function:

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

    // if user found, check the password
    if ($user && password_verify($password, $user['password'])) {

        // prevent session fixation attack
        session_regenerate_id();

        // set username in the session
        $_SESSION['username'] = $user['username'];
        $_SESSION['user_id']  = $user['id'];


        return true;
    }

    return false;
}Code language: PHP (php)

Note that you should call the session_regenerate_id() function to regenerate a new session id if when users start logging in. It helps prevent the session fixation attack.

The following defines the is_user_logged_in() function in the src/auth.php file, which returns true if a user is currently logged in:

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

If users have not logged in, you can redirect them to the login.php page. The following defines the require_login() function that redirects to the login.php page if the current user is not logged in:

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

And you can call the require_login() function at the beginning of any page that requires login.

For example, you can call it in the public/index.php page like this:

<?php

require __DIR__ . '/../src/bootstrap.php';
require_login();
?>Code language: PHP (php)

Log a user out

To log a user out, you need to remove the value you set when logged in and redirect to the login.php page.

In the login() function, you add the username to the $_SESSION variable. Therefore, you need to remove it to log the user out.

The following defines the logout() function in the auth.php that logs a user out by removing the username and user_id from the $_SESSION variable:

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

The following defines the current_user() function that returns the username of the currently logged in user:

function current_user()
{
    if (is_user_logged_in()) {
        return $_SESSION['username'];
    }
    return null;
}Code language: PHP (php)

Create the logout link

When users log in successfully, they are redirected to the index.php.

On the index.php, you can show a welcome message as well as a logout link like this:

<?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') ?>Code language: PHP (php)

When users click the logout link, you need to call the logout() function to log the users out.

To do that, you need to create the logout.php in the public folder.

In the logout.php, you need to call the logout() function as follows:

<?php

require __DIR__ . '/../src/bootstrap.php';
logout();Code language: PHP (php)

Redirect if users already logged in

If users already logged in and navigate to the login.php or register.php page, you need to redirect them to the index.php.

To do it, you can add the following code to the beginning of the login.php and register.php file in the src folder:


<?php

if (is_user_logged_in()) {
    redirect_to('index.php');
}Code language: HTML, XML (xml)

The following shows the login.php in the src folder:

<?php

if (is_user_logged_in()) {
    redirect_to('index.php');
}

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

if (is_post_request()) {

    // sanitize & validate user inputs
    [$inputs, $errors] = filter($_POST, [
        'username' => 'string | required',
        'password' => 'string | required'
    ]);

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

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

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

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

    // login successfully
    redirect_to('index.php');

} else if (is_get_request()) {
    [$errors, $inputs] = session_flash('errors', 'inputs');
}Code language: HTML, XML (xml)

Summary

  • Use the password_verify() function to verify the password.
  • Call the session_regenerate_id() function to prevent the session fixation attack.
  • Add one or more values (username and user idl) to the $_SESSION variable to mark if a user has been logged in.
Did you find this tutorial useful?