API authentication strategies in a Service Oriented Architecture

Photo by Alberto Barrera on Unsplash Introduction A key point to consider when designing distributed systems is the way our services communicate with each other. To ensure robustness and data consistency between services, both synchronous and asynchronous communications are usually employed.When talking about synchronous communications, a common solution is to use HTTP endpoints to grant access to […]

Photo by Alberto Barrera on Unsplash

Introduction

A key point to consider when designing distributed systems is the way our services communicate with each other.

To ensure robustness and data consistency between services, both synchronous and asynchronous communications are usually employed.
When talking about synchronous communications, a common solution is to use HTTP endpoints to grant access to specific business resources. But, as you may know, there is no easy way to manage the access to these endpoints. Suppose that our platform has two services, A and B, and each one of them manages the resources of a specific business domain. We have to deal with concepts such as authorization (which resources can be accessed and by whom), user authentication and authentication between services.

Since our platform here at Jobandtalent has multiple constantly evolving services, today we would like to review some strategies to improve API design and authentication between services by trying to answer questions like:

  • Should service A be able to access service B without authentication?
  • How do we translate domain-related permissions to APIs authentication?
  • Who will be in charge of aggregating data from different services to be displayed on the client side?

Authentication architectures

The first consideration relates to an architectural decision: having the authentication managed by each service or having it centralized.

On one hand, we can add an authentication layer to each service, acting as a proxy that accepts several authentication types.

With this solution, we are splitting the authentication responsibility across different services. This option might be valid if you only have a few services, or if the authentication differs greatly between services.
But we will probably end up duplicating logic and adding complexity to each one of our services.

Custom authentication layer per service

Choosing this option depends basically on how our system is structured and the number of services it has.

On the other hand, we can syndicate the authentication between the different services APIs, using an Authentication Provider, which can be internal or from a third party.

Centralized authentication

When we follow this pattern, each service has permissions on a specific set of endpoints, provided by the services that own the logic for each domain. This means every domain-related service needs to be explicit about what services it allows access to.

Let’s see with an example: suppose we have service A, which has a frontend interface (presentation layer) that retrieves and presents data from different backend services.

  • Service A sends authentication data to the Authentication Provider, requesting authentication to access service B.
  • The Authentication Provider validates the identify of service A. If the authentication is successful, the provider generates a token to identify the service against any other entity.
  • Service A receives the token and stores it to validate the following requests.
  • Service A calls an endpoint of a secured resource.
  • Service B checks the validity of the token with the Authentication Provider.
  • If the authentication is successful, the requested operation on that resource is allowed.

A common pitfall of this approach is the fact there is a single point of failure. If the Authentication Provider is down, this will stop our entire architecture from working.

However, this can be avoided by having multiple instances of the Authentication Provider distributed across different availability zones, which will mitigate the risk. Fault isolation zones and redundant components are key concepts in a high-availability application architecture.

10 years of the Jobandtalent platform

Authentication types

Now that we have reviewed the architecture approaches, and supposing that we choose the centralized authentication, let’s see different authentication types for communication between services.

  • API keys to authenticate requests between services. Each HTTP request from one service to another will include the API key, that will be validated by the accessed service. We may end up having an internal service (like the Authentication Provider explained above), that handles all API keys, which can be periodically rotated. Depending on our needs, we could add more complexity to this centralized API key management service.
    One of the main issues with the API keys is that they usually give us full access to all the CRUD (create, read, update and delete) operations that the API can perform. And this is not the best approach in terms of security. Furthermore, we need to query the database to validate if a key is valid or not. Besides, there is not a standard that regulates API keys format.
  • JSON Web Tokens (JWTs): a JWT, standardized in RFC 7519, defines both a token content and an encryption method. The origin service signs the JWT with a secret. The accessed service can verify the token with the same secret as the origin service.
    One advantage of this method is that it does not require database querying. And we can also include authorization info in the JWT (what resources are being accessed, etc).
    But these tokens cannot be easily revoked. Therefore, their expiration time is usually short, and revocation is handled via a refresh token.
  • OAuth standard-based solution: this option also allows you to handle user authentication and authorization (apart from authentication between services).
    → A third party authentication + authorization service like Auth0.
    → A directory service.
    → A social network. ex: Google, Facebook, etc.
    → An identification service: Github, Microsoft, etc.
    → A custom implementation of the OAuth standard.

Fortifying infrastructure

Another step further would be defining infrastructure-level rules that fortify access between services. This can be achieved at infrastructure or API gateway levels.

Apart from application-level security, we would add security at infrastructure-level (for instance, an AWS Security Group). If attackers access service A, they would not have access to perform more than a couple of calls to service B, not having access to any other service at the network level except service B.

This approach consists in isolating layers at the virtual network level, so that each service only accesses what it needs. There might be some issues due to the fact that the same cluster is used for different services.

For instance, if our hosting relies on AWS, there are ways (such us using awsvpc network mode) to manage security and authentication between services running in the same cluster. This mode in ECS-EC2 has a limit of ENIs imposed, that was increased recently. There is a potential low-limit, mostly depending on the number of containers expected to run per server. That does not happen in ECS-Fargate though.
We can also use task/instance profiles to limit the usage of other AWS services without using explicit credentials. Or we could even use IAM to generate permissions.

Additionally, we could use an API gateway that would talk to both presentation layer and backend services, being the single entry point for every request and redirecting to the appropriate service or aggregating information from different services and consolidating it into a format that the client understands. In Jobandtalent we have implemented this pattern with a Backend for Frontend (BFF) service.
The API gateway can also handle the authentication of the incoming requests.

Using an API gateway is especially useful if we want to set timeout policies for our clients or a common client-understandable format for our services’ error responses.
Furthermore, this pattern is very useful to enable us to reduce the risk of Denial of Service attacks (DDoS). Since the API gateway is the entryway to every request, our backend services will not be exposed to these kinds of attacks.

In any case, both solutions (infrastructure security and API gateway) are combinable.

Adopting Elixir: The BFF Case study

Conclusion

There is no golden rule to designing a good authentication system for a Service Oriented Architecture. There are several circumstances that need to be taken into account: number of requests, centralized authentication vs service-specific, an internal tool vs a third party authentication service…

Reasons to choose a strategy can be varied: from development productivity (having the authentication externalized simplifies things for the development team) to non-technical security reasons (our customers or stakeholders demand having all authentication systems within our network).

Hence, identifying the requirements that suit our business best is key before adopting one of these approaches. It is better to invest the time at the design phase than having to go back once the service is deployed and being used. This is even more important in an area as sensitive as authentication.

Acknowledgments: thanks to Sergio Espeja, Juanjo Martín, Luis Recuenco and John McLachlan for their feedback while writing this article.

We are hiring!

body[data-twttr-rendered=”true”] {background-color: transparent;}.twitter-tweet {margin: auto !important;}

function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height);resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === “#amp=1” && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: “amp”, type: “embed-size”, height: height}, “*”);}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind(‘rendered’, function (event) {notifyResize();}); twttr.events.bind(‘resize’, function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute(“width”)); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}

If you want to know more about how is work at Jobandtalent you can read the first impressions of some of our teammates on this blog post or visit our Twitter.


API authentication strategies in a Service Oriented Architecture was originally published in Jobandtalent Engineering on Medium, where people are continuing the conversation by highlighting and responding to this story.

Source: Jobandtalent