This chapter is about means to protect your application from anonymous users who could deliberately delete random data or pollute your application with spam messages. Until now, your application was publicly available to online audience without any possibility to control who is working with stored data. I want to show you how to store user account data securely (especially passwords) and how to verify (authenticate) a user which is trying to log into your application. I will not talk about different levels of user permissions because it would complicate things a lot – such feature is called authorisation.
It is not a safe approach to store passwords in their plain-text form. Such passwords can be viewed by anybody who has access to your database (maybe now it is only you, but in future it can be some of your colleagues or even your employees). A password is always saved in hashed form (a hash is a result of function which outputs a unique strings for different inputs and it is not trivial to reverse this process – i.e. to calculate original password from a hash).
Examples of hash function outputs in PHP for word “cat”:
Function password_hash()
is a bit different because its output is not always same like with the md5 or
sha1 functions. It is caused by a salt which is a sequence of random characters. The salt is used to make
hashes unique to avoid attackers from searching hashes online or using vocabulary attacks (try to
search for md5 hash e246c559bf94965d89cf207fc45905bc using Google –
d’oh). A salt is stored directly in the result of password_hash()
function.
To verify a password generated by password_hash()
use password_verify()
function.
You can also use a salt with the md5 or sha1 but you have to handle it by yourself (you will need another column in your database table to store it).
It is impossible to show/send user his original forgotten password due to hashing (but the security benefits of hashing
are more significant). If you need to have the ability of self managed passwords restoring in your application, you
should send a new password to email specified during registration process. You can use PHP’s mail()
function.
Even better way is to send unique password reset request link to his email, so the password is in fact restored
only when the user clicks this link from his email account.
This makes the email address of a user also a very sensitive information. Change of an email address should only be possible after confirmation of ownership of current email address – send unique token to his current email address to authorize changes.
To store a password during registration process:
hash = sha1(randomSalt + registrationPassword)
To verify a password when user tries to log-in (first fetch hash and salt from database):
if(sha1(databaseSalt + providedPassword) == databaseHash) {...OK...}
The probability that concatenation of user’s password and a random string used as a salt
will yield results in vocabulary search is very small. Yet use of sha1()
or md5()
functions
is strongly discouraged because powerful CPUs or GPUs can generate millions of hashes per second and
they are capable of breaking md5 or sha1 hashes within reasonable time (days). Recommended function
password_hash()
is also quite slow (by design – this is one of few instances when we want our
algorithms to be slow).
One of the reasons why any web application sends you a new password instead of yours when you forgot it, is hashing. They do not store your password in plain text form.
Create a table with login or email column and a column to store password in hashed format.
If you plan to use password_hash()
function, you do not need another column for salt.
Remember that login or email column must have unique constraint to prevent duplicate user accounts.
For PostgreSQL:
For MySQL:
Registration is actually not very much different from any other record insertion. Only difference is that you have to validate match of the passwords and calculate the hash. After successful insertion of the account record redirect the visitor to the login page.
It should have an input for login or email and two inputs for password verification (all inputs are required). You can use Bootstrap CSS styles.
File templates/register.latte
:
Use password_hash() function. Read the documentation because this function requires actually two input parameters. Second one is the algorithm which is used for password hash calculation.
File register.php
:
After successful registration, a record representing user account in the database should look like this:
User verification is also not a big problem – a person who wishes to log-in into your application has to visit login page with a simple two-input form. After he fills and submits the form, he is verified against the database. If an existing account is found and passwords match, your application can trust this user.
Actually there were cases when a user logged into another user’s account by a mistake – two different accounts had same passwords (not even salt can solve this situation). There are also online identity thefts when user’s password is compromised and used by someone else to harm original person. You can add another tier of user authentication, e.g. send an SMS to his cell phone to retype a verification code or distribute user certificates.
Create a login form and a PHP script to process login information. You can make error message a bit confusing to obfuscate existence of user accounts (sometimes you do not wish to easily reveal users of your app – especially when you use email address as login). For now, do not bother yourself by the fact that the confirmation is displayed only when the user sends his credentials. We will handle persistence of authentication flag later.
File templates/login.latte
:
Now we need to verify user information against the database and display errors when there are some. We will use the password_hash() counterpart function password_verify() similarly as in registration script:
File login.php
:
You probably noticed that there is no way to tell if a user has authenticated in subsequent HTTP requests due to stateless nature of HTTP protocol. To safely store login information you would probably want to logically connect subsequent HTTP request from one client (internet browser) and associate these requests with some kind of server storage. That is exactly what sessions are used for. A session is a server-side storage which is individual for each client. Client holds only unique key to this storage on its side (stored in cookies). Client is responsible for sending this key with every HTTP request. If the client “forgets” the key, data stored in session is lost. The key is actually called session ID.
To initiate work with session storage you have to call PHP function session_start()
in the beginning of each of your script (before you send any output, because session_start()
sends a cookie via
HTTP headers).
In PHP, there is as superglobal $_SESSION
array which is used to hold various data between HTTP request. These
data are stored on a server and cannot be modified by will of a visitor – it has to be done by your application’s
code. The $_SESSION
variable is initialized and eventually filled by session_start()
function.
Use $_SESSION
variable to store authenticated user’s data after login. Insert line with session_start();
function
into start.php script.
File login.php
(final version):
Extended file start.php
:
You can prevent anonymous users to access all your application’s functions or just selected ones. If a visitor tries to access prohibited function without authentication, he should be redirected to the login page.
Write a short include-script which will verify presence of user’s data in $_SESSION
array and redirect to login.php
script if no such data is found. Require this script in all PHP scripts where you want user authentication to be
performed before execution of that script itself. Place the require command just below the line where you require
start.php script.
File protect.php
:
Here is an example how to protect deletion of persons from database with created script:
File delete.php
:
You can also pass information about authenticated user into templates in protect.php
script. This would be useful
to modify your templates according to the presence of selected variable – e.g. you can show or hide menu buttons
which are not accessible to anonymous user. If you choose to make some modules of your application public, you should
pass user related variables in another way because protect.php
is not always executed. You can do that in start.php
after you start the session. Remember to handle non-existing values (for the case that the user is not logged in yet).
Finally, we have to give our users an option to leave our application. A logout action is usually just deletion of
all user related data from $_SESSION
variable on server.
Sometimes you wish to leave some data in the $_SESSION
variable – the contents of shopping cart, for example.
Use session_destroy()
function. Redirect user to public
page of your application after logout. Put logout button to your layout.latte template.
File logout.php
:
Make a link to logout script from main menu. You can display user’s login name inside the link.
User authentication or even authorisation is complicated. I demonstrated one of the easiest ways how to do it – you can also hardcode passwords into your source code (do not do that!). Keep in mind that weakest point of application is probably its user because people are lazy to fabricate new passwords for each website and they share some passwords with their family or friends. Security measures should also be designed according to the type of application you are developing (a bank account management application VS discussion board).
Passwords are sent over the network in plain text, it is a good idea to use HTTPS for (at least) registration and login pages to prevent attackers to sniff the password from network traffic. Still this does not prevent targeting users of your application by scam login pages which are used to steal their authentication information.
There is a lot more to explore: you probably know sites where you can persist your authentication for duration of days or even weeks – ours is forgotten as soon as the visitor closes his browser’s window. That can be achieved by setting special attributes to cookies which hold keys to a sessions. You also probably seen that some sites use global services like Facebook or Google to provide login functionality, which is good for users who do not want to remember too many passwords. Totally different approach of authentication is used for single page applications.
Remember that you are responsible for security of your application and also for the data of your users.