Welcome to the second part of my series on creating valuable, usable and future-proof Enterprise APIs. In the first post, I covered some background context and the importance of design; this chapter is about security and protecting your backend.
1. Secure your APIs
Most of the projects I’ve been involved with revolve around Cybersecurity, but I don’t consider myself biased when I say that security should be a top priority for every Enterprise API. Misconfigurations and attacks are common and you should go long ways to protect your customers and their data.
Besides the common table stakes (i.e. use TLS and some form of authentication), when it comes to API security I recommend considering at least the following:
- API Credentials: OAuth 2 should lead the way but, especially for on-premise products, API Keys are still commonly used. In any case, you should support multiple sets of API credentials in your product to allow for more granularity and proper auditing and compliance. For example, every individual service (or user) that consumes your API should have a dedicated set of credentials, which will make it easier to track down from the audit logs who/what performed an operation and apply proper rate limits.
- API RBAC (Role-Based Access Control): another important reason to support multiple sets of API credentials is the ability to assign different permissions to each set. This way your customers won’t give administrative privileges to a set of credentials used, for example, to perform only read-only routine operations. Having RBAC is not enough: you need to guide your customers to use it properly: a good solution is to enforce a flow in your product UI that discourages users from creating API credentials with unnecessary permissions (assigning permissions in an additive way rather than subtractive, for example).
- Credentials shouldn’t last forever: it’s a good practice to create sets of API credentials that expire at some point or require a manual reauthorization. For how long a credential should stay valid is a good question, as you want to make sure that a business process doesn’t fail because someone forgot to reauthorize something that was set up 6 months ago. There are ways to mitigate this risk in your product UI. I recommend showing a list of the API credential sets (without showing the actual secrets!), when they expire, and how long ago they were used for the last time. This will help customers identify and delete stale credentials and refresh expiring ones.
2. Protect your backend
Don’t trust your API users: they are likely to break things when given a chance! Not maliciously, of course, but there are so many things that can go wrong when you grant external users/applications access to the heart of your product through an API. And yes, bad guys and Denial of Service attacks exist too.
Similarly to Product UIs and CLI tools that validate user inputs and behavior, your APIs should do it too. But it goes beyond just making sure that all the required fields are present and correctly typed (if an API doesn’t do it properly, I would file a bug to get it fixed): include cost and performance in your design considerations.
Consider the following examples:
- Example 1: A proprietary API serving data from a Public Cloud database backend: in many cases such backends charge by the amount of data processed in each query. If your API allows consumers to freely search and return unlimited data from the backend, you might end up with an expensive bill.
- Example 2: An on-premise product with an API that allows users to provide files to be scanned for malicious behavior (i.e. a sandbox). A developer makes a mistake and forgets to add a sleep() in a cycle, hammering the API with tens of requests in a very short amount of time. If the API doesn’t protect the product from this situation, it will likely not just slow down the API service itself, but it can probably have significant performance impacts on the server and all the other services running on it, including your product and its UI.
Both examples are not uncommon for APIs designed exclusively for internal consumption (i.e. when if something goes wrong you can send an IM to the developer that is breaking your API and resolve the problem right away).
You should consider guidelines to mitigate these risk, such as:
- Shrink the result sets: try to support granularity when reading data from backends, for example by reducing the number of selected columns and slicing the results by time or other values. Set proper limits with sane defaults that don’t overload the backend. More on this in the next post.
- Rate limits: API rate limits are a practical way to tell your consumers that they need to slow down. Different consumers, identified with their sets of API credentials, should have their own limits, according to their strategic value, service level agreements, or monetization policy. Also, not all requests are equal: for example, some products implement rate limits using tokens. Each consumer has a limited number of tokens that replenish over time (or cost money) and each API call has a token cost that depends on a number of factors (i.e. impact on the backend load or business value of the service being consumed): once you run out of tokens you need to wait or buy more.
- Communicate errors properly: when hitting rate limits, API consumers should receive a proper error code from the server (i.e. 429 if using HTTP) and the error message should specify how long until new API calls can be accepted for this client: this won’t just improve DX, but will also allow the clients/SDKs to properly adapt and throttle their requests. Imagine that your API is used by a mobile app and they hit the rate limit: specifying how long they should wait before you are ready to accept a new request allows the app developer to show a popup in the UI to tell the end-user how long to wait. This will not just improve your API’s DX, but will empower the app developers to provide a better User Experience (UX) to their customers!
In the next chapter, I’ll dive deep into some technical suggestions on data-driven APIs and scale. In the last part of the series, I’ll share some more suggestions about monitoring your API and supporting the developer ecosystem.