APIs are the backbone of the modern data-driven economy. Whether you're moving your infrastructure to the cloud, shifting to a microservice based architecture, or just trying to integrate new services with your existing monolithic systems, chances are your business is relying on APIs to transfer your data. With the speed of business, it's easy for security to become an afterthought.
With your customer's and partner's relying on you to keep their data safe, it's important to make security a top priority when designing your APIs. With that in mind, here are ten tips for ensuring that your APIs are secure. While these tips are specifically aimed at REST APIs, many of them are also applicable to other types, such as GraphQL APIs.
Your APIs should only expose HTTPS endpoints. This protects credentials used to authenticate with your systems from being intercepted in transit, and helps to guarantee the integrity of the data being returned.
Avoid attempting to "roll-your-own" security solution for your APIs. Use a well established framework like Auth0, OpenId Connect, Amazon Cognito, or Firebase Authentication. These frameworks have teams of developers who have spent thousands of hours working out bugs and finding and fixing vulnerabilities.
If your API is cloud based, consider using the cloud platform's API Gateway service. This type of service provides fine grained controls over who can access your endpoints. All of the major cloud platforms have a gateway service available, and the additional layer of protection is well worth the cost.
If your exposed endpoints only use specific methods, whitelist the methods that are allowed, and reject all others. For example, if your API is read-only, you can whitelist the GET method, and reject any POST, PUT or DELETE requests.
Any input sent by the user must be validated and sanitized before it reaches your application logic. Be as strict as possible with your validation. Use strong types and regular expressions to make sure the input you receive matches what your application expects and is able to handle. Consider establishing a reasonable size limit for requests, and reject any larger requests. All failed validation should be logged and monitored for unusual request behavior.
Establish reasonable limits on the amount of requests you expect to receive from any single source. Any requests above this limit should be dropped. This can help prevent denial of service attacks on your systems. Depending on how your API is structured, you may wish to implement even finer controls by specifying different limits for different resources, or by adjusting the limits based on the client making the requests.
CORS stands for cross-origin resource sharing, and your settings determine how cross-domain requests are handled by browsers. If your API isn't expecting cross-origin requests, you should disable CORS. Otherwise, you should be as specific as possible with the domains that you allow cross-origin requests from. It's also important to note that CORS is strictly a client-side security feature, and should not be relied on, in any way, for server-side security.
No matter your development language, there's a pretty good chance you're using a framework to build your API. Whether it's Django, Rails, Spring or ASP.NET Core, nearly all modern API frameworks have some built-in security features that you should be taking advantage of, such as protection against attacks like cross-site request forgery and click-jacking, session security, or header validation. That being said, it's also important not to fall into the trap of believing that your framework's default security configuration is good enough. Take the time to fully understand the features available in your framework, and make sure your configuration maintains the principle of least privilege.
Any secrets (passwords, API keys, SSH keys, etc.) should never be checked in to your version control system. Even if your repository is private, it's still considered bad practice to keep your credentials with your source code. Instead, consider using environment variables for passing your secrets into your applications, or utilize a service specifically for storing secrets, such as Hashicorp's Vault. Most cloud platforms also have their own services for storing and retrieving credentials.
Make sure that any internal errors are caught and sanitized before being returned in your API responses. Your end users should never see any error messages that provide details on the architecture of your system. Raw error responses often contain a stack trace, and may expose information such as the database or libraries you're using, which an attacker may be able to use to exploit your system. Your error messages should be specific enough that the requester knows what happened, but generic enough as to not provide any details about the inner workings of your system. Also, make sure that your response uses the semantically appropriate HTTP status code for the given error.
When your customer's and associate's data is at stake, security should never be an afterthought. It needs to be included in the planning process from the very beginning. With these tips in mind, you should be on your way to designing a secure and reliable API for your users. For more information on securing your APIs, check out the REST Security Cheat Sheet from OWASP.