Like everything else that powers software infrastructure, APIs must be secured. That you know.
But do you understand how to go about securing an API? The API security process starts (or should start, at least) with API design. If you wait until you’ve already created your API to think about security, you’re fighting an uphill battle, because you’ll be trying to plug security holes that you could have prevented in the first place.
Keep in mind, of course, that designing an API that is totally flawless from a security perspective is impossible. You should never assume that your API is so well designed that it has no vulnerabilities. But you can, and should, take steps starting early in the design process to mitigate the risk of security problems within your API.
Toward that end, this article discusses strategies for designing an API that is secure from the start. (As we’ll see, this doesn’t mean, of course, that you need not worry about securing the API once it is created; API security also entails monitoring live APIs. But, again, secure API design is the first step toward API security.)
Best practices of API security
I'll be the first to admit that security can be a challenging subject to learn. As engineers, we're trained not to "over-engineer" solutions — don't solve Problem B before you solve Problem A. While that’s good advice for application development, the line between security and "over-security" isn't nearly as clear. When all an attacker has to do is find one way in, we, as defenders, must do our best to close every hole we can, which can feel incredibly overwhelming; especially if you are new to API security in general.
So with that in mind, what are some best practices for designing a secure API?
Security through obscurity
Growing up, my dad always used to tell me, "play your cards close to the vest." As an anxious kid, I had a habit of hyper-analyzing stressful situations that caused me to over-share my thoughts. In some ways, this instinct taught me the value of clear and honest communication, but in others it only dug me into a deeper hole. On more than one occasion, I talked myself into a lower grade on a test, or a ticket where only a warning was being presented. While it took me the better part of 20 years to understand exactly what my dad had meant, I've found that not only is this great advice for living, but it’s a great security principle as well.
What playing your cards close to the vest means in the context of security is that you should never give more information than users need. One common example of this type of "security through obscurity" is keeping error messages as generic as possible, but still valuable. This means keeping stack traces and exception messages out of the API responses and standardizing error messages across endpoints, which can go a long way towards preventing attackers from mining valuable data through over-specified error messages.
In addition to reducing information in API responses, reducing information in API requests can be valuable as well. Debugging is difficult without logs, but in a production environment, it is important to be sensitive to the balance between security and convenience. According to the OAuth 2.0 Threat Model and Security Considerations document, sensitive information should never be placed in the URL, as the URL and its query parameters are almost always transported and stored in plain text, which can lead to that data being "leaked" to untrusted destinations. A common method to help mitigate this vulnerability is to store sensitive request data in appropriate places, such as request headers; however, implementing encryption and using short-lived tokens — where appropriate — can go a long way towards ensuring the integrity of your request data.
Encryption, encryption, encryption
When it comes to API security, there is no excuse not to use encryption. If we're being honest, there's no excuse not to use encryption in any connected application, but ignoring it in API security is tantamount to cyber malpractice. Thanks to services like Let's Encrypt and Cloudflare, adding support for Transport Layer Security (TLS) to any web-based application is not only easy, but free. For the uninitiated, TLS is a cryptographic protocol designed to provide secure, encrypted communication over a computer network. In a nutshell, it encrypts data between an API client and an API server, which in turn prevents that data from being read if it is intercepted between Point A and Point B.
But, while TLS encrypts data in transit, it is also important to encrypt sensitive data at rest. In some instances, encryption at rest is impractical, as it can make data analysis and reporting difficult. However, encrypting things like API keys can add an extra layer of security in the event of a data breach. I should mention here that while encrypting sensitive data is a good practice, encrypted data is expected to be decryptable, so user passwords should never be encrypted. Instead, passwords should be stored using a strong, one-way cryptographic hash — and no, MD5 is not an accepted hashing algorithm.
Be kind, don't rewind
One of the biggest risks in API development is long-lived authorization mechanisms. The direct cause of replay attacks, long-lived authorization tokens can become compromised and used by attackers to "re-run" API requests — or craft new ones. While custom API key management is the go-to for many newer APIs due to its ease of use and low-overhead implementation, protocols like OAuth or JWT make securely managing short-lived API tokens much easier and significantly more secure when implemented properly.
Additionally, adding timestamps to HTTP request headers is a common technique for mitigating replay vulnerabilities. Commonly implemented as Hash-Based Message Authentication Code (HMAC), this authentication mechanism requires the API client to send a generated and hashed digest with each request — generally including short-lived data like a timestamp or nonce—which ensures that no two requests are the same (and thus not repeatable).
Don't trust, always verify
A good rule of thumb in most security is to never trust the user. While the risk of a malicious user isn't nonexistent, it is significantly lower than the risk of a careless one. Whenever your API receives user input, it should be validated and cleaned; SQL injection is still a risk in some environments, despite the best intentions of framework developers everywhere, so we must make an overt effort to prevent it.
Once user input is validated, the next step is to actually authorize the action the user is attempting to take in the first place. Some of the largest data breaches in history have been achieved through improperly guarded API endpoints, so every resource should verify whether or not the request is authorized to access it at all. In general, this is a good place to implement a whitelist, rather than a blacklist. Access to resources should be denied unless explicitly granted, rather than granting access unless explicitly denied.
Keep an eye on it
While good API security is about being proactive, identifying risks as they happen and analyzing incidents after the fact are also critical towards establishing a well-rounded security ethos. Keeping track of the data that is going in and coming out of your API through an application monitoring platform can go a long way towards identifying trends in your API's health. From resource allocation to data throughput, risky behavior is much easier to remediate when you are able to see it in the first place.
Perfect API security is a tough nut to crack, but that doesn't mean you should neglect it entirely. While the best practices listed above are a good start, they are hardly all-encompassing. The needs and goals of every API are different, so it is important to identify the risks your own API might be vulnerable to and mitigate them as much as possible. One thing that is worth recommending is to truly understand the inner workings of any framework or language you are working with, as many of them offer their own built-in security mechanisms that can go a long way towards closing some of the most common gaps in API security.