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:
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.