Make your own blog
Adding a login system
The next thing I decided to implement was a log-on system. This will allow a blog administrator to write, edit and delete posts, and to delete comments. In this set of changes, I add a users table, and allow the installer to create an admin user with a new password each time it is run. So, make the following changes, and then re-create the database:


- data/init.sql data/init.sql
- install.php install.php
- lib/install.php lib/install.php

93
94
95
96
'http://anotherexample.com/',
"This is a comment from Jonny"
)
;
93
94
95
96
97
98
99
100
101
102
103
104
'http://anotherexample.com/',
"This is a comment from Jonny"
)
;
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
username VARCHAR NOT NULL,
password VARCHAR NOT NULL,
created_at VARCHAR NOT NULL,
is_enabled BOOLEAN NOT NULL DEFAULT true
);
There are two fields in the new user table that I added based on my experience rather than an
immediate need. These are created_at
, which holds the date and time when the user
was first set up, and is_enabled
, which allows us to turn users on and off. Most
user systems will find a practical use for these simple features during their lifetime.
You'll have noticed a comment in lib/install.php noting that the password is stored in plaintext. This means that, as it stands, passwords would be stored literally, which is considered to be very bad practice indeed. What we should do is to store passwords in an encoded, non-reversible format, so that even if they are stolen they will be nearly impossible to read. This acts as a form of protection should a cracker get through our security and steal our database.
So, let's make that improvement straight away. To do so, we'll need the library I talked about
at the start of the tutorial, stored here in vendor/password_compat.
The features it provides are so useful they have been built into PHP 5.5, so if you are running
this version (or later) you can skip adding the new file and the associated
require_once
. However if you are running an earlier version, or if you are not
sure which version you have, add all of these changes.


- lib/install.php lib/install.php
- vendor/password_compat/lib/password.php vendor/password_compat/lib/password.php

1
2
3
4
113
114
115
116
117
118
119
120
121
122
123
124
125
<?php
/**
* Blog installer function
$error = 'Could not prepare the user creation';
}
// We're storing the password in plaintext, will fix that later
if (!$error)
{
$result = $stmt->execute(
array(
'username' => $username,
'password' => $password,
'created_at' => getSqlDateForNow(),
)
);
1
2
3
4
5
6
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
<?php
// Load the hashing library from the root of this project
require_once getRootPath() . '/vendor/password_compat/lib/password.php';
/**
* Blog installer function
$error = 'Could not prepare the user creation';
}
if (!$error)
{
// Create a hash of the password, to make a stolen user database (nearly) worthless
$hash = password_hash($password, PASSWORD_BCRYPT);
if ($hash === false)
{
$error = 'Password hashing failed';
}
}
// Insert user details, including hashed password
if (!$error)
{
$result = $stmt->execute(
array(
'username' => $username,
'password' => $hash,
'created_at' => getSqlDateForNow(),
)
);
So, what does the new code do? It makes use of a new function called
password_hash()
, which takes a password as input and produces what is known as a
hash. A hash is a mathematical calculation that is strictly one way, which means that
if sometimes steals our database of password hashes, they will find it very difficult indeed
to recreate the passwords they were generated from.
Whether or not you are using it, by all means do have a read of the source code in password.php. However, an in-depth exploration of it would be rather advanced at this stage, so for our purposes we will assume it just works.
The next step is to add a login form, and a link from which to access it:


- login.php login.php
- templates/title.php templates/title.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
<head>
<title>
A blog application | Login
</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
</head>
<body>
<?php require 'templates/title.php' ?>
<p>Login here:</p>
<form
method="post"
>
<p>
Username:
<input type="text" name="username" />
</p>
<p>
Password:
<input type="password" name="password" />
</p>
<input type="submit" name="submit" value="Login" />
</form>
</body>
</html>
In the next change, we add our familiar block of business logic before the main HTML. In this case, it checks to see if the form has been submitted; if it has, then it turns on the session system (more about that in a minute), creates a hash of the submitted password, and compares it with the hash stored in the database.
If the user gets their username wrong (e.g. it is not found) or the password wrong (the password hash does not match the one for the username supplied) then we regard this as a login failure. It is important for us not to be too helpful here (such as explaining that a username does not exist), as this information might be useful to a system cracker.
If the password matches however, then we call login()
to sign in the user, and
then we redirect to the home page using redirectAndExit()
.
What are sessions?
Sessions are an extremely useful feature that allow web applications to remember per-user information. By default, every request to the server is seen in isolation, so without sessions, an application would not be able to remember that a user had signed in, or what their username was.
To make sessions work, PHP sends the user's browser a cookie containing a random identifier, and for every subsequent visit, the browser supplies this back to the server. This identifier corresponds to a file on the server containing the variables that have been set for each user.
PHP makes this nice and simple for developers: we just turn on sessions using
session_start()
, and then we can just read and write to the$_SESSION
array. Easy peasy!
So, with that explained, let's add the changes:


- lib/common.php lib/common.php
- login.php login.php

121
122
123
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
function tryLogin(PDO $pdo, $username, $password)
{
$sql = "
SELECT
password
FROM
user
WHERE
username = :username
";
$stmt = $pdo->prepare($sql);
$stmt->execute(
array('username' => $username, )
);
// Get the hash from this row, and use the third-party hashing library to check it
$hash = $stmt->fetchColumn();
$success = password_verify($password, $hash);
return $success;
}
function login($username)
{
$_SESSION['logged_in_username'] = $username;
}
Now we have a way to determine whether users are logged in or not, let's switch that feature on for all pages. Here we also modify the HTML snippet that contains the page header, so it can show the appropriate login/logout link.


- index.php index.php
- lib/common.php lib/common.php
- login.php login.php
- templates/title.php templates/title.php
- view-post.php view-post.php

1
2
3
4
5
6
<?php
require_once 'lib/common.php';
// Connect to the database, run a query, handle errors
$pdo = getPDO();
$stmt = $pdo->query(
1
2
3
4
5
6
7
8
<?php
require_once 'lib/common.php';
session_start();
// Connect to the database, run a query, handle errors
$pdo = getPDO();
$stmt = $pdo->query(
You may have noticed, if you tried the logout link, that this page does not yet exist. So let's add that now in the following set of changes. Whilst we are at it, let's greet the user by their username while they are logged in, as this makes the experience of the site a bit more friendly.


- lib/common.php lib/common.php
- logout.php logout.php
- templates/title.php templates/title.php

149
150
151
152
153
154
$_SESSION['logged_in_username'] = $username;
}
function isLoggedIn()
{
return isset($_SESSION['logged_in_username']);
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
$_SESSION['logged_in_username'] = $username;
}
function logout()
{
unset($_SESSION['logged_in_username']);
}
function getAuthUser()
{
return isLoggedIn() ? $_SESSION['logged_in_username'] : null;
}
function isLoggedIn()
{
return isset($_SESSION['logged_in_username']);
That's all for this chapter, so give your application a good test, especially the new login and logout features. When you're ready, we'll proceed in the next chapter with some small tweaks to improve what we have.