Multitenant API Authentication with Gloo Edge
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.
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, 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).
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.
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, OutSystems Developer Cloud (ODC). You can learn more about it in our platform page.
Contributors
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.