Microservices architectures that expose a REST API are very common since they allow an easy and standard way to provide external systems (e.g. networked clients, programmatic access to end users, other services) access to your application. Any client that wants to interact with the API needs to prove its identity, in a process called authentication. There are many authentication methods, but HTTP authentication schemes are among the most used (e.g. basic, bearer, digest, or OAuth). The client presents an authentication token that must be validated by an Identity Provider (IdP) that usually is the same for all the API users. In multi-tenant environments we might need to have different IdPs per tenant, and this poses extra complexity on the way we design and implement a scalable system.

This post describes how to achieve API authentication in a multi-tenant microservices architecture where each tenant has its own Identity Provider using Gloo Edge as the API gateway.

hero-bp-multitenant-api-authentication-with-gloo-edge

Microservices Authentication

A microservices architecture is a distributed architecture. Each external request is handled by the API gateway and at least one service, which must implement some aspects of security. For instance, an ApplicationService must only allow a developer to see the applications of its organization, which requires a combination of authentication and authorization.

In order to implement security in a microservices architecture we must determine who is responsible for authenticating the user and who is responsible to authorize the user actions. The authentication is based on JSON Web Tokens (JWT), which are a standard way to carry verifiable identity information. The advantage of using JWTs is that since they are a standard format and cryptographically signed, they can usually be verified without contacting an external authentication server.

There are mainly two ways to handle authentication; at a service level or at the API gateway level

  • Authentication per service
    Each individual service authenticates the user given its credentials. The problem with this approach is that it permits unauthorized requests to enter the internal network. It relies on every development team correctly implementing security in all of their services. As a result, there’s an incremented risk of the system containing security vulnerabilities.
  • Authentication in the API Gateway
    A better approach is to let the API gateway authenticate a request before forwarding it to the services. Centralizing API authentication in the API gateway has the advantage that there’s only one place to get right. As a result, there’s a much smaller chance of a security vulnerability. Another benefit is that only the API gateway has to deal with the various different authentication mechanisms. It hides this complexity from the services, although they should also be part of the process. Each service should validate the JWT signature to make sure the token is valid and can be trusted.

API Authentication with Gloo Edge

We’ll be using Gloo Edge as our API Gateway. Let's consider the following scenario. We have a REST API available at https://www.mycompany.com/api, which is accessed by users of two tenants with different IdPs. Since all tenants share the same URL we need to distinguish their requests. To achieve that, each API HTTP request contains an X-TenandId header that identifies the tenant (this can be done at the CDN level. Check this AWS blog post on how OutSystems does it with CloudFront).

multitenant-api-authentication-with-gloo-edge-1

 

The most straightforward solution would be to have different routes per tenant and service, but that wouldn’t scale, since we would need (num_tenants x num_services) different route tables. A better solution is to delegate and chain route rules as described below.

multitenant-api-authentication-with-gloo-edge-2 

The VirtualService defines a set of route rules for a given domain (mycompany.com) and route (/api). It delegates the routing to the route tables labeled rt:tenants.

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: my-api
  namespace: microservices
spec:
  sslConfig:
    secretRef:
      name: api-ssl
      namespace: microservices
  virtualHost:
    domains:
      - mycompany.com
    routes:
      - delegateAction:
          selector:
            labels:
              rt: tenants 
            namespaces:
              - microservices
        matchers:
          - prefix: /api

A RouteTable is created for each tenant, which matches an header that’s unique per tenant. Each request must have the header X-TenantId which identifies the tenant. The inheritablePathMatchers ensures that this route inherits the path rules defined at the Virtual Service level. This route table provides authentication per tenant (using the extauth option) and delegates the routes to the services.

apiVersion: gateway.solo.io/v1
kind: RouteTable
metadata:
  labels:
    rt: tenants
  name: rt-tenant-<tenantid>
  namespace: microservices
spec:
  routes:
    - matchers:
        - headers:
            - name: X-TenantId
              value: <tenantid>
      inheritablePathMatchers: true 
      delegateAction:
        selector:
          labels:
            rt: services 
          namespaces:
            - microservices
      options:
        extauth:
          configRef:
            name: ten-<tenantid>
            namespace: microservices

Each tenant will be authenticated by a different provider, so we need to provide configuration in the form of an AuthConfig. In this object we configure the needed parameters for validating the JWT.

apiVersion: enterprise.gloo.solo.io/v1
kind: AuthConfig
metadata:
  name: ten-<tenantid>
  namespace: microservices
spec:
  configs:
  - oauth2:
      accessTokenValidation:
        jwt: 
          remoteJwks: 
            url: https://mycompany.com/auth/realms/<tenantid>/protocol/openid-connect/certs

The last RouteTable is the service route table, that routes by path for each service. It should inherit the header matcher from the tenants route table (through the inheritableMatchers attribute) but it should not inherit the path matchers because a new path is matched in this service route table (inheritablePathMatchers should be false).

apiVersion: gateway.solo.io/v1
kind: RouteTable
metadata:
  name: service1
  namespace: microservices
  labels:
    rt: services 
spec:
  routes:
    - matchers:
        - prefix: /api/v1/service1
      routeAction:
        single:
          upstream:
            name: service1
      inheritableMatchers: true 
      inheritablePathMatchers: false 

By default, a service will inherit the authentication configuration from the tenant route table. But there are some cases where an API may need a different authentication method (e.g. an API Key). In this case, the service can override the tenant authentication config and specify its own. For example, API calls to service S2 API may be authenticated with an API key and not with a JWT token like all the others. We can also have some unauthenticated API calls as shown in the example below, where we simply don’t define any options configuration.

apiVersion: gateway.solo.io/v1
kind: RouteTable
metadata:
  name: service2
  namespace: microservices
spec:
  routes:
    - matchers:
        - prefix: /api/v1/service2
      options:
        extauth:
          configRef:
            name: apikey-config
            namespace: microservices
      routeAction:
        single:
          upstream:
            name: service2
      inheritableMatchers: true 
      inheritablePathMatchers: false
    - matchers:
        - prefix: /api/v1/service2-unauth
      options:
        extauth: {}
      routeAction:
        single:
          upstream:
            name: service2
      inheritableMatchers: true 
      inheritablePathMatchers: false

Finally

Implementing multitenant authentication with Gloo Edge can be both easy and scalable. If you have a microservices architecture where each tenant is authenticated with a different identity provider, this will make your life easier. Here, at OutSystems, we’ve been busy using the approach outlined in this post to build the authentication service of our next-generation multitenant cloud-native product, Project Neo. You can learn more about it in our platform page. 

Contributors

Pedro Nunes 

Pedro Nunes
Principal Cloud Architect

As a Principal Cloud Architect, Pedro is always focused on improving and moving the OutSystems cloud to the next level.
Outside work, music is his other passion. And of course hanging out with family and friends.