Authorization using bearer token in a multi-tenant software product

Duo Yao
5 min readDec 8, 2021

--

Our team has been working on a service to provide authorization to our multi-tenant software product. This service needs to expose endpoints to handle authorization token operations and role-entitlement CRUD operations. After research, the team decided to develop the authorization service based on OAuth 2.0 specification and using its Bearer Token for authorization.

How is the token customized for multi-tenancy

To accommodate the multi-tenant nature of the product, we introduced a number of custom token attributes, including org_id, org_name , scopes , type and access_profile .

A token structure example can be seen as below:

Token attributes overview

  • org_name and org_id: the organization name and ID tracks the active organization’s name and ID
  • scopes: a scope is used to describe the group of related set of entitlements, the scopes attribute represents the scopes the token holder belongs to.
  • access_profile: the access profile has an org_access field which describes the list of entitlements the token holder has for the respective organization. The url attribute is used to query a full list of organization access this user has.
  • type: this field indicates the type of this token. It can be either “compact” or “extended”. A compact token indicates the token contains complete access profile information; an extended token indicates this token is trimmed. To obtain the complete token, one can make a GET request to the access profile URL.

How roles, scopes and entitlements are used to authorize a user

A role is a user oriented attribute; users assigned to a role are indirectly granted the set of entitlements associated with the role. A role is assigned to a user upon registration and is bound to this user through its lifetime. In our system, we have a “role entitlement table” to track relations between roles and entitlements.

A scope is resource oriented. Each system resource is assigned with one or more scopes. This is meant to simplify the representation of a related set of entitlements for this system resource. In our system, we have an “entitlement table” which also tracks the scope for each entitlement.

As mentioned at beginning of this article, we authorize a user based on OAuth bearer token. How are the entitlements determined for an authorizing user ? When a user is requesting authorization (obtaining a bearer token) to a resource, the authorizing client, such as the UI, will make a token request to the authorization service. This request has the resource scope information and the user role information. Based on the scope and role, the authorization service determines the collection of entitlements and responds to the client with the bearer token which has these entitlements embedded. Once the user has been granted with the bearer token, the client can use this token to access the respective system resources.

The flow discussed above is illustrated by the diagram below.

The authorization service

With the authorization flow and token structure determined. The team developed the authorization service.

The authorization service handles responsibilities including:

  • Manage authorization related artifacts (roles, entitlements, access profile etc) via CRUD APIs
  • Manage OAuth client credentials via CRUD APIs
  • Token generation, revocation and introspect

The authorization service currently supports two grant types: client credential and authorization code .

Client Credential Grant

The client credential flow is used to authorize applications on behalf of an authenticated user. When an external application needs to access resource of a given organization, an administrator will need to login to the organization UI and generate a client ID and secret pair for this application. This client credential needs to be securely copied over to the application for it to request the bearer token from the authorization service.

Authorization Code Grant

The authorization code grant is used to authorize users. In this flow, a user need to be authenticated first to obtain an identity token. Using this identity token, the user needs to request an authorization code from the authorization service; then with the authorization code, user shall make a subsequent request to obtain its bearer token.

The authorization SDK

With the authorization service generating the bearer token, one may ask how would a resource respect the generated the token ? The authorization SDK comes into play for this responsibility.

Our backend services are REST based. In order for the rest endpoints to check the incoming request has the correct bearer token, it needs to verify below items:

  • the token signature needs to be valid
  • the token must not be expired
  • the token issuer and audience needs to be correct
  • the token access profile need to include all the required entitlements

The team has designed and implemented a token validator which performs all the above validations. The design considerations include:

  • the validator shall support multiple token types
  • the validator shall be easily applied to the REST endpoints
  • the validator shall be easy to configure and easy to invoke

The token validator middleware

Considering the requirements and given our backend services are REST based, the team decided to implement the validator as a middleware layer.

To illustrate how the token validator middleware meets the requirements, we can start with an example below:

It simply takes one line of configuration before it can be inserted as a middleware for endpoints. The middleware config contains wrapper constructors to initialize the middleware itself and validators.

The TokenValidatorMiddleware takes a variable number of validators as arguments, including

  • IdentityTokenValidator
  • BearerTokenValidator

The TokenValidatorMiddleware provides two functions:

  • ValidateAll: all validators must pass for this to pass
  • ValidateAny: at least one validator pass for this to pass

If there is ever a need to support additional token validations, we can simply develop another validator implementing the same token validator interface and insert it to the validator middleware, levering both the O and I of the SOLID principles. If it is ever need to support more complex validation conditions, it can also be achieved by adding additional middleware methods.

Summary

It has been a great learning experience and great team collaboration to bring the work from RFC ideas to production code. Although there are more functionalities still yet to be added, writing about our design thinking is still worthwhile and should benefit both myself and future readers.

--

--

No responses yet