When we think of compromised API security, we typically see an image of a hoodie-wearing hacker toiling away in a darkened room, targeting a bank’s server. We can imagine them compiling a custom tool, launching it against the system, and celebrating as your 401k becomes theirs. Alternatively, we may even picture armies of hackers using sophisticated tools and zero day exploits to steal our bank account information, our healthcare records, and other identifiable information.
These commonly held stereotypes couldn’t be farther from the truth. According to the Open Web Application Security Project (OWASP) API Security Top 10 list, the vast majority of API breaches come down to simple mistakes from the people designing, building, and deploying our APIs. This makes for both good and bad news.
The bad news is that—once again—we’re our own worst enemy. Our permeating “move fast and break things” mindset has opened a world of risk that we weren’t ready for. Meanwhile, the good news is that we currently have the tools, practices, and ability to address these problems.
Instead of reviewing the whole list, in this post, I will focus in on two key risk categories: broken authentication (#2) and broken authorization (#1 and #5).
A quick reminder before we proceed: authentication establishes who you are, while authorization establishes what you can do. People often think of them interchangeably, but they’re not equivalent.
Broken API authentication
When it comes to API security, there are a number of ways where authentication can go wrong. Here are a few of the things that need to be done for effective authentication:
1. Incorporate authentication
Anonymous APIs that are open to any unauthenticated user are a relic of the past. Your API needs to have some form of authentication, even if you intend to charge for it, both for security and support and maintenance reasons. Regardless of your technology stack, you can start with a simple API key.
2. Protect user credentials
Unfortunately, some APIs still put the key or access token on the url itself. While that approach is quick and easy, it presents a huge vulnerability. This is because the entire url is logged in every cache, access log, and load balancer between your client application and the API server. That means the first time anyone uses the API, their account would be compromised.
Instead of in the url, you should put the key or token into the authorization header. Not only is it more secure, but it abides by a recognizable standard. Using standards is faster, easier, and more secure.
3. Improve on the key
Just like with any password, the API key should be long and unique. Unlike normal passwords, keys aren’t ever typed, so they can be as long as you want them to be. As the key will be stored in a configuration file, the only limit you might have is aesthetic. Alternatively, if you switch to a JSON Web Token that’s commonly used with OAUth, the token could be thousands of characters long. It may be cumbersome to copy and paste, but it will be more secure.
4. The final step: Key rotation
No matter what you do, your API key will be compromised at some point. It could be due to a developer accidentally adding it to a Github repository, sloppiness as the key is reused in multiple projects, or from someone hacking your applications. Fundamentally, the circumstances and intent don’t matter as the result and mitigation strategy are the same: you need to be able invalidate and generate a new key quickly and easily.
If you’re using a simple key, you can probably have a webpage within your admin dashboard to generate a new key. While this works, you should go further. If you use OAuth 2.0 to generate an access token, not only will you meet all the above requirements, you’ll also gain the concept of a refresh token. In this instance, whenever your access token expires, you can exchange the refresh token for a new access token. Once again, using standards is faster, easier, and more secure.
Now that you have a better idea of how to enhance your authentication, let’s consider authentication.
Broken API authorization
Authorization is a much bigger and more complex problem to consider. OWASP’s Top 10 list breaks it into two segments: broken object-level authorization and broken function-level authorization. This means we need to consider both the data and functionality that our API shares.
1. Consider API permissions from day one
Time and time again, when people design APIs, their API keys grant full access to everything, at every time. In fact, most quick API framework tools grant full access by default; this is careless, at best. An API will solve a variety of use cases for a variety of applications—if you give all of them the same permissions, some applications will have far too much access. At minimum, we should separate read vs. write access for each of your API endpoints or resources.
2. Think about ownership within your API
By this I don’t mean copyright and patents, but who owns and can therefore access each individual record or object within the system. At the simplest level, we need to make sure that any given user can only see their account data. Before granting access to information, you should make sure it’s owned by the person requesting it.
In recent profile breaches, attackers simply used their own record ID and counted up or down to retrieve others. It’s painfully clear in hindsight, but counting has become the single most common, devastating, and effective attack against our APIs.
3. Stop counting
When users, account numbers, or other objects in the system have a simple incrementing identifier, attackers can predict and try them all. Instead of counting, we should generate unique identifiers (UUIDs) which are unique and impossible to guess.
4. Detect, log, and respond to attacks
If we can detect attackers trying different attacks, we can respond by limiting or deactivating access while an attack is underway. While not strictly an authorization issue, it still ranks #10 on the Top 10 list.
5. Limit permissions based on the user and their context
Not all applications are created equal or address the same use cases. As we combine read/write access and data ownership, we need to evaluate API authorization policies based on identity and context. For example, if our API is for a payroll system, we need to consider which scenario is legitimate:
- Mike approving raises on his company-issued laptop at his desk at 10am on a Monday; or
- Mike approving raises on his personal phone from Las Vegas at 2am on a Saturday
If we can build authorization policies that take into account the device, the network, the geography, the time of day, and the overall use case, our systems will be more secure and more reliable for our intended users.
Building and protecting customer trust
Fundamentally, all of the principles and practices boil down to a simple responsibility: customers have entrusted us with data and we must protect it.
If you fail to protect your customers, nothing else matters: your developer experience will be irrelevant. Your well-designed SDKs will be worthless. And your beautiful documentation will describe a broken and insecure system.
As you build new APIs, it takes minimal effort and no down time to implement better authentication and authorization earlier. Unfortunately, most companies already have insecure APIs lurking in their infrastructure and those will be more challenging to remedy without causing disruption to their users.
To learn more about API security, check out our webinar, API Security Threats and How to Combat Them, or see the below resources: