The GitHub Security Lab audits open source projects for security vulnerabilities and helps maintainers fix them. Recently, we passed the milestone of 500 CVEs disclosed. Let’s take a trip down memory lane with a review of some noteworthy CVEs!
This post is part seven of GitHub Security Lab’s series on the OWASP Top 10 Proactive Controls, where we provide practical guidance for OSS developers on proactively improving your security posture. How can you robustly assert and identify a user’s identity? Guidance from the OWASP Proactive Controls: C6
One of the toughest problems is figuring out who’s who online.
We’re here to help offer some guidance about how to securely implement authentication (that is, how to verify digital identity) into your app. In the blog post below, we’ll discuss ways that will help you securely implement these features into your application.
OWASP breaks down the three main approaches to managing digital identity into the following criteria:
- Multi-factor authentication (MFA)
- Cryptographic-based authentication
Given the current authentication landscape, we’re adding another category:
- Single sign-on (SSO)
We’ll discuss single sign-on first, as it can be the simplest form of authentication to implement into your application.
The simplest way to assert whether a user is who they say they are is to have a trusted third party confirm their identity. Technically speaking, this means having your users sign into a platform with a robust authentication platform (like, GitHub, Google, or Apple), then having that platform tell your application “this user is <some user>.”
By using SSO, neither you nor your users need to deal with having an additional password. You also don’t need to deal with any of the infrastructure needed to verify a user’s digital identity. Instead, this is the responsibility of a service provider.
The main tradeoff with using SSO is that you need to trust the identity provider. However, generally speaking, identity providers tend to have robust security resources dedicated to protecting user authentication. As such, in most cases we’d advise you to consider using an authentication provider as opposed to writing your own authentication.
There’s a couple of ways to implement this into your application. The most common way for a web application is to use OAuth or SAML. Check out our documentation for more information about how to use GitHub for OAuth!
Next, let’s discuss the oldest and most common way of handling authentication—passwords.
When handling user accounts and identities, the first thing that usually comes to mind is passwords. Passwords are the way most users are accustomed to creating a new account, and rely on a user generating a secret string, which they present to the application each time they want to be authenticated.
There’s a couple of key issues to look out for when using passwords in your application as follows:
Easily guessable passwords can lead to account compromises, so you need to ensure that your users are setting strong credentials.
Provide guardrails to ensure users set strong passwords, without requirements that upset or confuse them.
It’s important to strike a proper balance between a positive user experience and good security! Studies have shown that complexity requirements on passwords have mixed effectiveness, so you’re better off removing abstract character requirements in favor of longer passwords.
OWASP’s guidance for good password guardrails are the following:
- Ensure all passwords are at least 10 characters in length.
- Allow all printable ASCII characters.
- Block commonly-used and compromised passwords.
Allowing user-friendly, longer passwords means that your users will be happier, more likely to remember them, and less vulnerable to a brute force attack.
This is a good place to mention that you should not prevent paste in password fields. This is extremely user unfriendly, and makes it harder for users to use tooling, like password managers, incentivizing bad passwords.
Remember, user friendly comes first!
We’ve all forgotten our passwords at some point. Without them, how can we securely assert our identity? We can do this by using an alternate method, like multi-factor authentication!
We’ll discuss MFA in more depth later, but in a nutshell, in a secure MFA implementation, you should have a user prove their identity by using a combination of the following:
- Have them use something they know.
- For example, have them answer a security question.
- For further guidance, specifically about security questions, check out the OWASP forgotten password cheat sheets.
- Have them use something they own.
- For example, send a reset token to a device or other account that they own (email).
Make sure to provide a user friendly flow to reset their password using some combination of the above methods.
Another good idea is to provide some instructions in your password recovery flow if the user did not expect to receive a password recovery email (for example, please change your password immediately).
Passwords are the most critical part of a user asserting their identity. As such, we need to treat them carefully in our infrastructure. Especially with password dumps seemingly becoming more common every day, it’s extremely important to keep in mind the following.
Storing passwords in plain text means that any user that gets access to your password database will be able to login as any other user. In the event of a breach, this would be catastrophic.
On the other hand, only handling properly-hashed passwords ensures that a malicious user that does get access to the password database will need to spend an enormous amount of time trying to brute force each password.
In order to properly implement this, keep in mind two things:
- Choose the right hashing algorithm.
- Cryptography is tough! So, it’s important to use an algorithm/library that has been vetted by security experts and is well used.
- A good hashing algorithm should be slow from both a CPU and GPU perspective (to prevent brute forcing a password).
- We’d recommend using Argon2 or Bcrypt.
- Salt your passwords.
- Attackers have access to rainbow tables, which are massive databases of precomputed hash to string pairs. This can be used to map hashed versions of passwords back to their strings (for commonly-used strings). In order to add entropy to the hashes and thus make it less likely that the rainbow table contains the mapping, you should add a random string, known as a salt, to user passwords when hashing. See the following blog post for more information.
Given an infinite number of guesses, any password can technically be guessed. To reduce the probability of a successful password being tried, implement rate limiting or lockout protections for a certain number of failed attempts. Dial this according to existing policies and sensitivity of the data you are protecting.
For example, you could set up a counter to only allow five failed login attempts in an hour. Then, lock the account, and let the user know of the failed attempts via email.
To implement this, it’s important to track the number of failed logins for any given resource. You can use Redis, or another similar tool, to implement rate limiting.
This also helps prevent credential stuffing attacks, where leaked credentials from another site are tried against several other websites.
Also consider adding monitoring services to reactively inform you when a resource is being attacked! Time is everything in security, so having automated monitoring can be the difference between a successful attack and one that is thwarted.
Even the best password can sometimes be compromised. Next, we’ll talk about ways to protect your users beyond the use of passwords.
What happens if your users have their passwords compromised? Not all is lost, as long as they have MFA enabled!
MFA means that users have to produce multiple pieces of information (factors) in order to be granted access to your app. This also means that an attacker would need more than just a password to take control of a user’s account.
As mentioned previously, MFA consists of either something a user knows, or something that a user owns. As such, there’s a variety of methods that you can use to implement this.
Make sure not to support insecure methods, such as texting a user with a token. Texting a user is vulnerable to several kinds of attacks, such as spoofing, phishing, and SIM swapping. As such, keep things safe and easy for your user by only allowing one of the above options.
As an example of how important this is to protecting your users, GitHub recently announced that we will require all users that contribute code on GitHub.com to enable one or more methods of two-factor authentication (2FA) on their accounts.
Finally, let’s take a look at how to securely handle your user’s identity using cryptographic-based authentication.
Now that you’ve created the proper guardrails, and your users are behaving securely, the last pillar of securing your user’s identity is to ensure that your application is properly handling your user’s identity with cryptographic-based authentication. This can happen in two main ways:
A “session” is a server side object that stores information about who the current user is, when they logged in, and other aspects of their identity. Then, in order to identify the user, we typically put a cookie on the user’s browser that contains a session identifier, which maps back to the server side identity object.
Some things to ensure when implementing session authentication are:
- Make sure to use secure configuration settings for the session cookie.
- In most cases, you should set the `http-only`, `secure`, and `samesite=lax` flags.
- Ensure that the session identifier is long, unique, and random.
- Generate a new session each time the user re-authenticates.
Tokens are an alternative form of authentication, where instead of storing anything on the server side, we cryptographically sign a piece of data and give it to the client. Then, the client can give us this signed piece of data, and we can thus ensure that we successfully authenticated them.
Token authentication can be tricky to implement safely, so we’d recommend doing more research before implementing this yourself. However, some pointers to start, include:
- Use a long, random secret key.
- The entire security posture of your application relies on your tokens being generated from a secure key.
- Having a weak secret key will leave you vulnerable to a brute force attack, where an attacker can try to guess your secret key, and thus hack all users at once.
- Use a secure cryptographic algorithm, like ES256.
- Set an appropriate expiration date with your tokens, based on your application’s usage patterns.
- Store your tokens in a secure place.
- For a mobile app, you can use KeyStore (for Android) or KeyChain (for iOS).
- For a web app, there are several places where you can store the token, each with different security considerations. For general guidance, we would recommend storing tokens in a cookie, with the `http-only`, `Secure` and `SameSite=Lax` being set.
As mentioned previously, using token authentication in a web app can be tricky, and best practices differ from framework to framework, so please do further research before implementing this yourself.
Securely verifying your user’s identity can be tricky, but following the guidance above can help prevent common issues to keep your application and your users secure. Until next time, stay secure!