So you're building an application and finally got to the stage where you need to worry about user authentication. Maybe you're thinking about rolling out your own identity solution and trying to figure out where to start? Or maybe you already have a solution and are now wondering if it's enough. Regardless, implementing authentication correctly can easily become a monumental and risky task.
Broken authentication is consistently on the Top 10 Web Application Security Risks list.
Application functions related to authentication and session management are often implemented incorrectly, allowing attackers to compromise passwords, keys, or session tokens, or to exploit other implementation flaws to assume other users’ identities temporarily or permanently.
It's easy to underestimate all of the work that goes into building a secure AND user-friendly authentication system. Let's look at some commonly overlooked features as well as some potential attack surfaces that may be missing in your identity solution.
What is the first thing that comes to your mind when someone says "authentication"?
For a lot of people, it boils down to this:
Verifying an identity with username and password.
Verifying login credentials is just the tip of the iceberg when it comes to building an identity solution, but it's a great place to get started.
When a user signs up for your application, they generally have to create some set of credentials that they can use to identify themselves when they want to sign in again.
This means that your application must securely store these credentials in a database somewhere at the time of creation. When a user comes back and tries to sign in again, you need to check their provided credentials against those in your database.
Simple, right? Not so fast. This straightforward task can open up a can of worms if done incorrectly. Here are some things to consider:
- How will you store and protect the credentials?
- Are there any requirements for password creation? Length? Complexity?
- What will you do if someone forgets their login credentials?
Let's explore some of these questions while looking at the role password safety plays in an identity solution.
When it comes to protecting the credentials you're storing, hashing is essential.
Hashing is a one-way transformation of data accomplished with an algorithm based on a mathematical computation called a hashing function.
According to OWASP, hashing functions must have the following properties:
- It's easy and practical to compute the hash, but "difficult or impossible to re-generate the original input if only the hash value is known."
- It's difficult to create an initial input that would match a specific desired output.
So how does this relate to password safety?
When a user signs up for your application, you need to store their username and password in your database in a way that minimizes risks. If your user database is ever stolen, you don't want the attacker to have access to usernames and plaintext passwords! Hashing your passwords is one way to minimize this risk.
When they enter their password, you send it through a secure hashing function and store the output in your database with their username.
Then when the user comes back to sign in again, you hash the password they type and compare it to the hashed password stored in the database. If they match, then the password is correct!
One downfall of this approach is that if Person A and Person B have the same password, their hashed passwords will also be the same. So, if an attacker gets a hold of the database and cracks Person A's password manually, then they also know Person B's password.
This is where password salting shines.
A salt is additional data that can be added to password input before hashing.
Adding a random salt to every password before hashing would ensure that Person A and Person B would have unique hashes, despite their plaintext passwords being the same.
If you'd like to learn more about hashing and salting, check out this excellent series: Hashing Passwords One Way Road to Security.
Password reset flow
Now that your passwords are hashed and salted, let's look at how to handle password recovery.
It's fairly common for users to forget their login credentials. This is a great example of a scenario where your authentication solution will need to tip-toe the fine line between great user experience and security.
Here is a good password recovery process to follow:
- User clicks "Forgot password"
- They enter their email address
- The authentication server sends a one-time password (OTP) to the email address
- The user checks their email and clicks the link or enters the OTP on the website
- The application checks that the OTP matches, and if so, lets the user reset their password
Once you have the flow down, you also have to consider how you're creating the OTP. When does the link expire? Did you make sure the token is random and long enough so that it can't be guessed?
Password safety attack surfaces
This is not even close to an exhaustive list of what's required to enforce and maintain safe passwords. But even just these two points, hashing and password recovery, can open up several attack surfaces if implemented incorrectly or not implemented at all. Let's look at a couple of them.
Credential stuffing attack — If your user database is breached AND credentials are stored in plaintext or with a weak hashing algorithm, an attacker not only now has easy access to all of your user accounts, but they also can use your database to run a credential stuffing attack. Because password reuse is so prevalent, there's a good chance some of those credentials are active on other websites. Attackers will sell these stolen user credentials to bad actors who will try to gain access to user accounts across the web.
Username enumeration — Another type of overlooked attack surface is those created by error messages. Password recovery is a great example of this. An attacker enters an email address and clicks "Forgot password". If your application returns the message "Sorry, this email address does not exist", then the attacker knows that the user doesn't have an account. Now, if they enter another email address and don't receive that message, they know the email does exist. They can then use other methods to attempt to access the account since they know the account definitely exists.
Dictionary attack — This is a form of brute force attack where an attacker tries to access user accounts by programmatically entering common passwords. If your authentication solution doesn't enforce strong and complex passwords, there's a good chance many of your users will use weak passwords that are easy to compromise.
It's tough to manage all of this on your own, but using a service such as Auth0 allows you to offload a lot of that responsibility! User credentials are handled and stored on Auth0's end, so instead of worrying about keeping up to date with the latest hashing functions and password protection methods, you can focus on building what's unique about your own application.
Once you have your password safety locked down, you may start to ask yourself, is this even enough?
In the previous section, everything covered revolved around single-factor authentication. Even if you do everything right, there's still only one factor required to access an account: the password.
With multi-factor authentication, you add extra layers of protection by requiring more than one factor to authenticate.
Generally, these factors fall into one of the following categories:
What you know — Something you know, such as a password.
What you have — A physical item you have, such as a cell phone.
What you are — Biometric data, such as fingerprint, retina scan, etc.
An example of multi-factor authentication would be requiring a user to enter their username and password to sign in, and then sending a one-time password to their cell phone, which they have to send back to be authenticated.
In this scenario, even if an attacker knows the user's password, it isn't enough. They'll have to also gain access to their phone to provide the OTP.
Implementing multi-factor authentication
Multi-factor authentication solves a lot of problems and can greatly reduce your application's attack surfaces! So what are some of the "gotchas" when it comes to implementing multi-factor authentication?
One-time password generation — There are a few things to keep in mind when implementing one-time password checks. How many times can a user request a new OTP before being locked out? How long should the OTP remain active? What recovery options will you offer if the user loses their phone? Because of the complexity and risk of implementing MFA, most people will offload this to an external service that already excels at it.
User experience — The biggest downside to multi-factor authentication is that it can sometimes come at the expense of user experience. If you're requiring your users to download obscure applications to receive an OTP because that was the easiest solution to implement, you will probably lose them as a customer. Additionally, every factor that you require adds more complexity to your application, which can inadvertently introduce bugs. If you offer several methods for your users to authenticate, you better make sure you can support all of them and keep them up to date with the best security practices.
With Auth0, you can choose from several multi-factor authentication options and turn them on with a single click!
To manage the balance between user experience and security, some people find it best not to require MFA on every login, but instead, require it when accessing more sensitive data.
With step-up authentication, applications that allow access to different types of resources can require users to authenticate with a stronger authentication mechanism to access sensitive resources.
For example, say you have an application that requires users to sign in with email and password to access customer data. However, when a user wants to see employee data, an extra authentication factor is required.
Using Auth0 Rules, implementing step-up authentication is a breeze.
Scaling Your Solution
Another tricky situation to navigate is scaling your authentication solution. Let's look at some possible roadblocks you could run into as your application gets larger.
Scaling for volume
As your application grows in popularity, the number of users you support may also increase as well. If you require your users to log in to interact with your application, you never want them to get the frustrating message that your authentication server is down!
A common way to scale an application is to add more servers to handle heavier loads of traffic. A load balancer sits in front of these servers and directs traffic among them so that no individual server receives too many requests. This can cause problems if you're using sessions to handle authentication.
Imagine a user accesses your website and is directed to Server A. They sign in there, so their session information is stored on Server A. At some point while they're clicking around your website, they get sent to Server B. Now the server will have no idea who they are because their session isn't stored in that server's memory.
There are several ways to avoid this, such as sticky sessions or storing state elsewhere like a separate database. Nevertheless, it's important to be aware of these gotchas if you implement a session-based authentication solution.
Scaling across stacks
Another issue you may run into as your application grows is how to scale across stacks. Maybe you started with a simple Laravel application that handled all authentication needs on the backend. Later on, you decide that the Laravel Blade templates are just not enough, so you create a new frontend React application and leave Laravel as the backend.
Now you have a whole new beast to deal with: SPA authentication. One of the biggest challenges with JSON Web Tokens and single-page applications is knowing where to store tokens.
When a user signs in to your application, the client is issued an access token. Then when the client makes a request to your API, it must also send along that access token so that the server knows whether or not the client is authorized to receive the response. Storing this token in
sessionStorage could result in token theft through a cross-site scripting attack.
- Cross-site scripting (XSS) — A type of attack where malicious scripts are injected into a website and served unknowingly to end-users.
To see an example of how a session cookie can be stolen with an XSS attack, check out Developer's Guide to Common Vulnerabilities.
This is just one example of how expanding your application across stacks can add a lot of complexity to your authentication solution. It's important to make sure that you're knowledgeable about the stack you end up with and that the solution you build or buy is adaptable and extensible.
A great authentication solution won't just play defense; it will also include features that get ahead of potential attacks.
Brute force protection
Back in the Password Safety section of this article, you saw that despite taking several precautions, there are still some attacks that can be carried out to gain access to user accounts. One of these attacks is called a brute force attack.
A brute force attack involves an attacker using trial-and-error to guess login credentials.
Instead of letting anyone just go wild with login attempts, it's best to limit the number of times any specific IP can attempt to sign in to the same account. To accomplish this, your solution must be able to detect the IP attempting to sign in and effectively rate limit them.
You should also offer password recovery options in case it's a legitimate user who just can't remember their password.
Even better, you may want to notify the affected user that someone has attempted to access their account several times. This can prompt the user to set up extra precautions on their account, such as MFA, to protect themselves against the attack.
Breached password detection
Another advanced way to get ahead of attacks is by warning a user if their password is weak or has been exposed in a breach.
Massive data breaches happen all the time. These breaches often contain user login credentials, which are sold to the highest bidder or even published online for anyone to grab. Because password reuse is extremely common, there's a good chance that an attacker can use one set of credentials across multiple websites.
This is known as a credential stuffing attack.
The best way to prevent this is to always use different complex passwords for every account. Of course, as an application developer, you can't force your users to pick a unique password for your application. Instead, you can try to protect your users by notifying them if the password they're using was involved in a breach.
This kind of anomaly detection requires substantial ongoing work. You have to maintain a list of up-to-date published password breaches and check if any of your users' credentials exist in this list. If they do, you need to warn the user that they should reset their password. Until the password is reset, you should also block any attempted logins to that account.
Anomaly detection options
With Auth0 anomaly detection, you get access to automatic brute force protection, breached password detection, and more.
You can test this out yourself by signing up for a free Auth0 account right now!
User Management and Insights
One last feature to think about is user management. You may have an airtight identity solution in place, but is it going the extra mile?
For most businesses, it's important to know who your users are and how you can effectively manage them. The natural next step for an application that allows users to sign in is to create a dashboard to explore and manage those users.
For example, you may want to search for all users who haven't logged into your application in over six months and send out an email to all of them to nudge them to interact again.
Or perhaps you run a discussion forum, and you decide you want to ban or limit a user who is being unkind. Of course, you could do this manually without building out those features, but maybe your coworker, who isn't a developer, is in charge of user management. How would they do it?
In these scenarios, a user dashboard comes in handy. This wouldn't be too difficult to build overall, but it would certainly take a lot of time.
Auth0 comes with a robust user management dashboard that gives you tools to analyze and manage your users. Cumbersome tasks like account linking, password resetting, and user banning can all be done right from the dashboard!
In addition, you can view valuable user analytics, such as:
- The number of times the user has logged in
- Last login date
- And more!
This was by no means an exhaustive list of what's required to build an authentication solution. But even these few points reveal that rolling out your own authentication system is no easy task! Even if you use pre-built libraries to implement it on your own, you still have to stay on top of updates and the latest standards. When it comes to something as sensitive as user security, the work is never finished.
Implementing an all-in-one authentication and authorization solution like Auth0 can save your team time, money, and A LOT of stress. And even better, offloading a task as substantial as identity gives your team more time to work on the features that make your application unique.
Auth0 provides a platform to authenticate, authorize, and secure access for applications, devices, and users. Security and application teams rely on Auth0's simplicity, extensibility, and expertise to make identity work for everyone. Safeguarding billions of login transactions each month, Auth0 secures identities so innovators can innovate, and empowers global enterprises to deliver trusted, superior digital experiences to their customers around the world.