Securing Large API Ecosystems
Most companies rely on APIs these days. But if small APIs are easily managed and protected, more complex ones need different approaches. As API complexity grows, security solutions should also be developed to match it.
Nowadays, APIs are ubiquitous — there is no doubt about that. As with almost every product, though, many aspects of APIs become more complex as they grow. Companies often use APIs that are small enough to be easily managed and secured. For many others, however, the API landscape is growing exponentially, introducing new challenges to consider.
It’s worth noting that such an expansion can occur in different directions. For example, APIs expand in depth in companies that heavily rely on microservices. One request to a publicly exposed endpoint ends up being forwarded to many different services, either in parallel, when one service aggregates data from a few others, or in a chain, where one service calls another, which then, in turn, calls another, and so on. APIs can also expand in breadth. Sometimes, an API exposes tens or even hundreds of endpoints via one entry point.
As API complexity grows, security solutions often remain too simple to sufficiently secure a larger ecosystem. Even though many companies have matured their API security with access tokens issued using OAuth, solely using OAuth and access tokens might not be sufficient for large API landscapes. Often, all access tokens issued for an API have the same properties, meaning that a bearer of one token has the same privileges as a bearer of a different token. This can lead to a few problems.
Issues of Breadth-Grown APIs
If an API exposes many endpoints, the problem is that the bearer of such an access token can successfully call any endpoint. Usually, when proper authorization checks are in place, the bearer of such a token will not be able to retrieve a different user’s information. Still, such a bearer may be able to call endpoints that you might not want them to. For example, a client that should only be allowed to read user data — perhaps because the documented endpoints only allow for read access — starts calling endpoints to update user data.
Some companies try to remediate this problem by introducing scopes that limit an access token’s capabilities. This is one possible solution, but it may produce additional issues. For large APIs, it’s usually hard to develop a good set of scopes that divide functionality into logical groups. There is also the need to manage scopes and the endpoints they correspond with. Large APIs are vulnerable to scope explosion, a situation where too many scopes are created. This makes access control challenging to manage and confuses users, who might not understand the client’s permissions.
When scopes allow access to large portions of your API, the same problem may occur as described earlier: A client might call resources with a given scope even though you do not want it to.
Issues of Depth-Grown APIs
Some APIs have a “deep” architectural design, in which many services are called during processing of one request. When one token is used to secure all the downstream services, the following situation may occur:
- Service A calls service B with the access token received from the caller.
- Service B can use the token to properly grant authorization to the caller.
- Now, Service B can also call any other endpoint in the API and perform an action that only the original client was permitted to perform.
This situation can become particularly dangerous if downstream services in the request include third-party services outside your infrastructure. In such a situation, not only does the token leave your infrastructure, but it is now in the hands of a third party, which can use it to call your API.
Change the Optics
Consider the way access tokens and their contents are perceived. (This isn’t a security problem per se, but it might affect architectural choices). With access tokens, we often try to discern who the user calling the API is and whether that user is authenticated (logged in). However, we should remember that different clients can call APIs. Even though a user has given consent to a client to act in their name, the user might not operate the client at all — it may be a background process, for example.
With that in mind, access tokens should not be perceived as similar to a user’s browser session. When dealing with access tokens, you should not be concerned with the identity, but rather have the access token answer what the bearer can do with this access token, or in which user’s name the action is performed.
For example, a user might give consent to an app to aggregate their calendars. The app will call the calendar API in the background from a backend service. The calendar API should thus not assume the user’s identity based solely on the subject claim of the access token. The API should be aware that the user might not have initiated each request, even if they all contain the user’s credentials.
Scopes are a type of claim commonly used in access tokens, but they should not be the sole source for authorization decisions. The audience claim (“aud”) is a good example of another claim that systems can use to easily limit API access. With the audience claim, the API can check whether the access token was intended for a specific audience and reject tokens meant for a different audience. Authorization servers are capable of issuing claims based on complex information from the authentication process. Each API can then use these claims to make advanced authorization decisions.
For example, the previously described calendar API could use claims with information about the client to know whether a request comes from a background service or a mobile application operated by the user.
You can read more about best practices for designing scopes and claims or check how complex claims can be issued in the Curity Identity Server.
Implementing Token-Sharing Approaches
It’s crucial that in large APIs, especially those large in-depth, the token is not reused across all services invoked to process a request. Teams should consider implementing token sharing in a way that best suits the situation at hand.
This usually means implementing token exchange, a standard that enables a service to obtain a new access based on its current one. Token exchange limits the potential actions the downstream service can perform with the token. However, embedding an access token might also be a viable option for some companies.
Managing Authorization Complexity
In large API ecosystems, managing authorization rules can become tricky. Each service — no matter if it processes the incoming request or if it is called by another service — should know which claims to consider and what actions should be allowed based on those claims. Getting this wrong leads to broken function-level authorization, which in 2019 was listed as one of the OWASP top 10 API vulnerabilities.
To solve this issue, companies can implement entitlement management systems to help manage the complexity of authorization and provide separation of concerns with authorization rules being handled in a centralized way. Open Policy Agent is one example of an open source authorization engine.
Conclusion: How to Secure APIs
Security is always important and hard to accomplish, and APIs are no exception. When securing APIs, solutions that work for small deployments may be insufficient in larger ecosystems. Security features should also not be tailored just to the type of product in question.
Implementing a feature just because it’s recommended for APIs might not always be enough. The scale of operation should also be considered when making architectural decisions, as it will render some features more or less critical for a system or introduce a need for a more intrinsic configuration. When designing APIs, companies should consider these points to appropriately choose the right security solutions from the available options.