Building software in distributed components has a lot of benefits. It's easier to maintain and refactor, theres a clean separation of concerns, and it's easier to split into multiple teams to get work done. But how do you manage common tasks like Authentication sanely in such an architecture? We built an API Gateway in Nginx that starts to solve some of these issues.
3. 3
How do we develop products?
• API-centric architecture
• Resource-oriented APIs, future plans for Hypermedia
• Web UIs are JavaScript apps talking to APIs
• Modular APIs and codebases
Hi, I’m Andy Winder from Message Systems, and I wanted to talk today about some of the work we’re doing, and how we structure that work into distributed components under one API.
Message Systems writes software for handling large customers email management needs, companies like Facebook, LinkedIn, and Twitter. Our software manages the sending and receiving of emails, as well as other types of messages like push notifications and SMS messages. We’re increasingly focused on users interacting with our systems over REST APIs instead of SMTP, as was traditionally the main interface into email systems.
We build Resource-Oriented APIs and are beginning to focus on really supporting hypermedia interactivity within our APIs. We don’t build APIs as a second thought, they’re integral and first-class citizens, and we need them to be able to build browser apps for our customers as well. We don’t want to build them into one big codebase though, we want to build small sub-APIs that compose our larger API offering.
Tons of smaller components need to be managed, and we need smart ways to share common functionality across these smaller APIs so that we don’t reinvent the wheel everywhere.
Different internal teams use different languages and tool chains to produce their APIs. We needed a way to build common functionality in a sharable way, and then expose a collection of APIs as a more unified offering to users. We also needed for developers of all these APIs to be able to easily integrate in something like authentication in a consistent way across the API.
We decided to use Nginx to build an API gateway and expose our smaller API components as one unified API. The API Gateway acts as a midpoint between the backend APIs and the user request. Nginx, and a distribution called OpenResty, allows for not only using Nginx as a proxy, but writing middleware in Lua to manipulate requests and responses in different phases of nginx handling a request. Internal APIs, on the right side, are only exposed inside the network. We can build smarts into the gateway to extend these internal APIs.
Layer7, 3Scale, Mashery and Apigee offer pre-built API gateways. These gateways offer very robust feature sets, but when we investigated the offerings, we saw some tradeoffs in complexity, ability to integrate at key points, and they were all paid offerings. Some offerings involve cloud solutions only, and can carry performance costs as well. We had simple upfront needs and explored building a gateway solution for our use-cases to see what the level of effort would be to replicate what we needed.
Let developers use your APIs in their web applications without needing to be on the same domain. Using Nginx middleware, we intercept the request and add in CORS headers to all requests. CORS involves a preflight OPTIONS request to the URI specified, so we build that into nginx too. Now all of our APIs are CORS compliant, and using the same logic, but that logic only exists in one place. Less room for error, and less overhead for developers.
Authentication and Authorization work in a similar way. When requests begin, a Lua routine runs and inspects the API key (in the Authorization header) from the client request. It calls an internal API endpoint to figure out if the request is valid or invalid. Invalid authentication, or a valid API key with no access to the requested endpoint, have error messages returned to the user. Successful authentication & authorization pulls out user details from the API key and adds them as X- headers, and then passes control to the right API endpoint.
The benefit of this approach is that no single API component needs to understand the mechanics of authentication & authorization, but all API components are protected by authentication by default. API code is orthogonal to the gateway functionality. We can make big changes to the internal mechanics of authentication, as long as we fulfill the contract of passing X- headers along to underlying APIs, and they won’t know that anything has changed.
To wrap up, we use these methods to organize our highly-distributed architecture into a well-defined structure, with shared functionality “wrapping” our APIs at the nginx layer. We’re not tossing all our functionality for the entire organization into one big pile of blocks, we’re building it into small, organized, and well-separated pieces.