Login pages – they’re everywhere. Almost every website has a login page – from big companies to discussion forums.
In this blog post I’ll try to explain how login pages work and also show how to create one, but instead of spoonfeeding information, I will only explain the core concept. I will also clarify how to mitigate two types of attacks that can occur if your website has a login functionality. Let’s begin, shall we?
A login form – the basics
A basic login form has three input fields – one for a username or an email address, one for a password and one for a login button.
My login form (shown below) has four of them – one of the fields is used to mitigate an attack that is often overlooked.
The majority of login pages have one inherent weakness that is Cross-Site Request Forgery. I am not going to cover what it is as I already did that in a previous blog post but rather I will try to explain how it can be mitigated on a login page so your users won’t be able to log other users (or yourself) in without permission.
Login CSRF – A basic concept
Login Cross-Site Request Forgery is such an attack that may occur on a login form.
Since the majority of login pages do not protect against this type of attack, it is easy for an attacker to log a user in by knowing the appropriate POST parameters – the browser would send a POST request to the page containing the login credentials and if they are correct, the user would be logged in.
My login form has 6 files:
- A configuration file which defines the database connection information
- The main file (index) which contains the login form itself and defines what should happen when the “Log In” button is clicked
- The login lockout file that locks a user out after more than 5 unsuccessful login attempts
- The file that protects the form from Cross-Site Request Forgery (CSRF) attacks
- The file that a user is redirected to after successful login
- The logout file that handles the logout process.
I’ll start from the top and move towards the bottom, but before I start I would like to highlight one critical point: You should be submitting and delivering the form over HTTPS because if your form isn’t delivered over a secure channel, passwords typed in the form could be stolen by an attacker. More information on HTTPS can be found here.
The configuration file should contain the host name, which is used to connect to your server, your database user name, your database user password and a database name.
When you create a variable that connects to your database and successfully connect to it, an instance of the PDO (assuming you’re using it) class will be returned to your script. To destroy the connection, simply assign NULL to the variable that holds the object.
The main file
The index should contain four input fields – one for a username or an email address, one for a password, one for a hidden CSRF token and one for a login button. Name your fields accordingly, but leave the CSRF token’s value blank for now – we’ll come back to it later.
Your database should have a field for a username or an email address, two fields for a password hash and its salt (a salt is random data that is used as an additional input to a hash), one field for login attempts that will be reset upon successful login, one “lockout” field where you will check whether the user is locked out of the form or not and one field for defining the lockout period.
Also if you’re considering storing passwords in plain text, please don’t – if you do so, the security of your service could be very easily compromised. Hash the passwords instead – hashing is the act of converting passwords into unreadable strings of characters. Since some hashing functions are more resilent to cracking than others, I’d recommend you use BCrypt.
After you’ve designed your input fields and database structure, you need to do one more thing – you need to create a process that happens after you click the “Log In” button:
- Check whether the username or email address exists in the database or not.
- If the username or an email address do exist, check the lockout field. If the lockout field is empty or if the lockout value deducted from the current time is less than zero, proceed to the third step.
- Hash the provided password and compare it with the password hash in your database. If the hashes match, start a session and redirect the user to the index. If they don’t, throw an incorrect username or password error and include the login lockout file.
The login lockout file should update the database with a query to increment the login attempts each time a user is attempting to log in.
If the user attempts to log in more than 5 times and fails but is not locked out, set the lockout value in the database to “1” or “Locked Out” – this prevents bruteforce attacks. You should also set the lockout time to the current time plus the amount of minutes you want the user to be locked out. In my login form the login lockout time is set to the current time plus 5 minutes. That’s five minutes of lockout. Yours might be different.
The file should also check whether the user is attempting to login while the lockout value is set to “1”. If this is the case, reset the login attempts, throw an error and forbid logging in until his lockout time has expired.
A cross-site request forgery attack can be prevented by generating tokens. A properly generated token should be:
- Unique per session, so it changes when the page is reloaded
- Random, so it cannot be guessed
- Generated by a cryptographically secure random value generator, so the generated token cannot be predicted – if you’re using PHP, the openssl_random_pseudo_bytes() function should help you achieve this.
If the token in the hidden field matches with the token in a session, unset the session token and let the user log in. If it does not, block the request. In my case, the token is added as a hidden field value within the login form. It can also be added as a GET parameter.
After you have started a session and redirected the user to the main page, you can greet him by returning “Hi, ” and his session name. From this point, you can do multiple other things too – that may be a topic for another day though.
Logging a user out
Logging the user out is rather simple – destroy his session and redirect the user back to the index.
- Login forms should use TLS – HTTP requests should be encrypted
- Have a login functionality? Hash the passwords
- Login Cross-Site Request Forgery can be prevented by generating random tokens
- To log a user out, destroy the session.
A demo version of my login form is available here.