As web developers, we're all usually pretty security-conscious and aware of the dangers of poor security practices. We know that we need to prevent SQL injection, CSRF attacks, XSS scripting attacks, and so on. However, these are usually security features that happen behind the scenes without the user's knowledge.
So in this article, we're going to take a slightly different approach and take a high-level look at some security features that users are fully aware of and can even interact with. The types of features that we'll discuss can help to:
- Encourage our users to stay security-conscious
- Help our users to be proactive with their own security practices
- Give confidence to our users that we take their security seriously
Provide Multi-factor Authentication
A great security feature that you can add to your application is MFA (multi-factor authentication), meaning that the user must provide more than one authentication factor when signing in. You may also sometimes see the term 2FA (two-factor authentication) which specifically refers to 2 authentication factors being used.
Without getting too heavy into the specifics, "factors" are a type of authentication that can be used to prove your identity when signing into a system. The most common type of factor that you'll be familiar with is passwords, which fall under the "something you know" category.
Factors typically fall into one of 5 categories:
- Something you know - Such as passwords, or PINs.
- Something you have - Hardware authentication such as a YubiKey, or token generated by a physical device.
- Something you are - Biometrics, such as fingerprints or face scanning.
- Somewhere you are - Such as checking you're in the same geographical location you'd typically sign in to your account.
- Something you do - Such as performing an action on screen that only you'd know.
Typically, when signing into an account on a web application that has MFA enabled, you're usually required to enter your password (something you know) and then asked to provide an additional factor (most often, something you have or something you are). In a large majority of web applications, the second factor would be something like a code/token (something you have). Depending on the application, you may need to provide a code that is generated using an authenticator app (such as Google Authenticator), or it could be sent to you via SMS or email. As a side note, for any Laravel developers reading this, you can check out my Send an SMS in Laravel Using Vonage (Previously Nexmo) article if you're interested in being able to send these codes via SMS from your application.
On some applications, I have also seen hardware authentication features available. This can make it really handy if you want to set up a YubiKey as an additional factor.
The benefit of using multiple factors is that it reduces the chances of someone gaining access to your account if a single factor is compromised. For example, let's say that a leaked password list is being passed around the internet and a malicious actor gets a hold of it. If they were to attempt to sign in to your account using just your password, they'd be able to gain access if you didn't have MFA set up. But if you had MFA enabled, they'd only be able to get halfway through the authentication process. Without access to something like your actual phone, email account, or token generator (sometimes provided by banks to generate codes), they wouldn't be able to gain full access.
So, by providing MFA functionality in your application for your users, I think you can instil confidence in your users' minds and let them know that you take their security seriously. It can also give them an extra sense of confidence that their data will likely be safe in the event of someone trying to get into their account.
However, it's important to remember that if you provide some type of MFA you may also want to provide some form of recovery functionality, such as one-time use recovery codes. These recovery codes could be downloaded by the user when first setting up the MFA and stored on something like a USB drive. The recovery codes act as a fallback in the event that one of the factors can't be accessed anymore. For example, let's say your user is using Google Authenticator for their second factor. If they were to lose their phone, that would mean they'd be totally locked out of their account. So, by allowing them to use some recovery codes that they've stored in a safe place, they can still get access.
Send Suspicious Activity Notifications
Another useful security feature that you could add to your application could be to send notifications to your users whenever you detect any type of suspicious activity related to their account.
For example, you could maybe send an email or SMS to your user if the user signs in from a different location than they've logged in from before.
Although a user logging in from a different location isn't always a sign of an issue, it can be used to alert the user if it's in fact a malicious hacker that has gained access to the user's account.
Provide Session Management Features
Expanding on the point above, your application could also implement some form of session management. For example, this could be a page in your application that shows all the current authenticated sessions that the user has. It could display information for each session, such as location, browser, operating system, device type, and IP address. But it'd be worthwhile remembering that some of this information can be spoofed, so it shouldn't be treated as the absolute truth, but rather as an indication for the user.
The session management could also provide the user with the functionality to manually destroy individual sessions or all the sessions at once. I've personally used this type of feature after I signed in to my Facebook account on a friend's computer and forgot to sign out. So after I got home, I was able to manually destroy the session so that I would be signed out. This meant that I could be proactive in securing my account rather than hoping that my friend wouldn't do anything malicious (or prank me!).
Log the User's Activity
Depending on the type of application you're building, logging your users' activity to an audit log can be a useful security feature.
Firstly, this could be useful because a user could use the log to spot any suspicious activity that indicates their account may have been compromised by a malicious hacker.
The audit log could also be used in the (hopefully, unlikely) event of an account being breached. It could allow the user to conduct an investigation afterwards of what the hacker may have done with the information on the system. Did they steal any user data? Did they download any reports? Was any client information lost?
What you record in your activity log will differ depending on your application, but you may want to keep track of events such as:
- Successful sign-ins.
- Suspicious sign-ins.
- Multiple attempted sign-in failures.
- Downloaded reports and user data.
- Changes in user information (such as email addresses).
- Data entities they may have created, updated, or deleted.
Send Emails When the User's Email Changes
Another handy security feature that you could implement in your application would be to send an email whenever a user's email address is updated. You could send an email to the current email address and the new address, letting them know that it's being updated in your application.
It's very possible that the user may just be updating their email address to a newer one. However, having this feature could help alert the user if a malicious hacker has got into the system and is updating it to a different one to lock the user out.
Depending on how this feature is implemented, you may even want to add a confirmation link to these emails so that the user can confirm they want their address to be updated. However, this particular part of the feature could cause issues if the user is updating their address because they no longer have access to the email account that they currently have on file for your system. So this is something that would need to be thought about on a project-by-project basis.
Similarly, you could also implement this feature to alert the user if their phone number is updated too.
Notify When the User's Password is Reset
You may also want to alert your users whenever their password is reset.
Like some of the other security features above, this could send emails or SMS messages to your users whenever their password is updated. This would mean that if a malicious hacker has got access to their account and is trying to lock the user out, the user could be alerted instantly.
Checked for Pwned Passwords
When implementing your registration forms and password reset forms, you may want to make use of the Have I Been Pwned API. By integrating with this API, you'll be able to check whether the password that the user has entered can be found within any known list of passwords that have been compromised in the past.
This means that you'll be able to proactively let your users know if their passwords are no longer safe to use and prevent them from signing up with it. Not only can this help the users, it could also be a convenient way of reducing support tickets for yourself. In the event that a user's account is breached, they might reach out to you to ask for support. So by reducing the chances of it happening, you can reduce the amount of time answering support tickets and carry on building cool new features for your application.
For details on how to use the Have I Been Pwned API, I'd recommend checking out their API documentation here.
Regularly Check For Up-to-date Information
If you use GitHub, you'll likely have noticed the reminder that is shown to you when you sign in periodically. This reminder usually asks you to double-check that all your information is up to date, such as your email, backup email, phone number, two-factor authentication method, and recovery codes.
You may also want to implement a feature like this in your system. It allows your users to review their own security and prompt them to keep the details up to date. For example, let's imagine that a user gets a new phone number but has forgotten to update their information on your system. Presenting a modal or page to your users with their current security information could remind them to update their phone number that they'd previously forgotten to do.
Depending on your application, you may want to implement this periodically, such as every month, every 3 months, or maybe every 5th login.
Allow Granular Permissions for API Tokens
In a lot of projects that I've worked on that have implemented permissions, I've usually found that the API permissions were an afterthought. Typically, I found that a huge amount of effort was put into implementing the roles and permissions systems for the web UI, but that the same effort wasn't put into the API because "no one's really going to use it apart from a few clients". This can be a very scary situation to be in, knowing that there is a glaringly obvious security hole in the API.
So it's important to make sure that the correct roles and permissions checks are implemented within your API to ensure that users can only access what they're supposed to be allowed to access.
To further improve this, adding the ability to have multiple API tokens with granular scopes/permissions can be extremely powerful. For example, if you've ever had to create a new API token on GitHub, you'll have seen the checkboxes on the page that allow you to select what that API token is allowed to do. For instance, you may want to have a token that's only allowed to read data. Or, you may want to have a token that's only allowed to create and delete data entities, but not delete them.
Adding the ability for your users to create tokens that can only perform specific actions can encourage them to follow the principle of least privilege. In fact, this is something that I discuss in Battle Ready Laravel that I think should be helpful. So rather than the user having a single "master" token for their user account on your system, they could create multiple API tokens, each with their own specific purpose.
As a developer, I can say that being able to do this is a very reassuring feature that I like using because it gives me the confidence that the fallout is restricted if a single API token is compromised. It means that instead of the API token being able to be used to access every part of the API, they'll only be able to access the part that the token was created for.
If you wanted to take this further, by having multiple tokens, you could add a feature similar to "session management" so the users could manually expire the tokens to stop them from being used. If you implemented an activity log, you could also record which API token was used to perform a given action to help improve the quality of your audit trail.
Hopefully, this post should have given you a brief overview of some different features that you can provide in your web applications to improve your users' security.
If you enjoyed reading this post, I'd love to hear about it. Likewise, if you have any feedback to improve the future ones, I'd also love to hear that too.
You might also be interested in checking out my 220+ page ebook "Battle Ready Laravel" which covers similar topics in more depth.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter.
Keep on building awesome stuff! 🚀