Skip to content

System architecture and design

ronan edited this page Jul 22, 2021 · 2 revisions

System architecture

The system design consists of 4 separate REST web services hosted on a single server. There is a public facing API that acts as the intermediary between a user and the separate sub-services.

Each of these sub-services have been designed to handle separate related functionality. This was done so for both maintenance and security.

The idea here is that these sub-services can be further isolated and deployed to different servers, limiting the risk of complete breach. By separating the encryption keys and api keys, this adds a further layer of security, even though any encryption uses runtime values that only the user possesses.

The ‘Public API’ is responsible for retrieving the different credentials from the separate data persistence layers and using this to decrypt the relevant tokens.

By ‘isolating’ the services into functional groups and separating data persistence layer, these services can be maintained and extended with much more ease as well as allowing greater control in terms of access restriction on the ‘Public API’ layer.

Public API:

Linx REST web service which exposes a public API for generating and retrieving access tokens.

This service acts as a “buffer” layer between the external world and hidden authentication services. This service does not persist any data and acts as a facilitator between the user and the different sub-services that persist data.

This service is responsible for the runtime encryption/decryption of access tokens and the sending and receiving of this data to the relevant data storage services. Requests made to these subsystems are authenticated using an internal API Key. This is done to firstly place some layer of security on the service between the public API and the sub-services. Secondly, in cases where you want to split up the sub-services onto their own servers, this allows a simple way of applying security.

When a token retrieval request is received, a request is made to the ‘user admin API’ to validate the API Key. The user admin service responds with the encryption key associated with the service provider and API Key. This key data is passed through to the operation of the request to be used to encrypt and decrypt the access tokens.

User administration service:

Generic standalone service to manage and validate user related API keys. This service is responsible for the generation of API Keys and encryption keys used for encrypting/decrypting the access tokens.

Token storage service:

This service is responsible for the initial storage of the state and encryption key and subsequent storage and retrieval of access tokens. The idea here is that this is purely a database layer that stores and sends data without business logic.

OAuth service:

Generic standalone service to generate the redirection url and handle the exchange of the authorization code for the access tokens. This service managed the authorization server related tasks and credentials.

Process flow:

I came up with the following flow keeping in mind that at no point was a credential value to be stored in plain text and any decryption/encryption needed to use runtime user provided key values. All of the below is achieved using the low-code platform Linx.

API Key registration: Users register for a new API Key using their login credentials. Once validated, a new user API Key is generated as well as a corresponding encryption key which is used for encrypting any access tokens associated with the API key. The API Key is hashed, and the ‘encryption key’ is then encrypted with the plain text value of API Key. This means that only with the original API Key (which is never stored on the server anywhere) you are able to decrypt the ‘encryption key’. The plain text value of API Key is returned to the user to save for future use.

App authorization:

To initiate the OAuth 2.0 Authorization Code Grant Flow, a request needs to be initiated by a user in a browser to an authorization endpoint specific to that service provider as well as the connecting application.

To initiate the flow using the Linx service, a user makes a request to the Public API submitting their API Key generated in the previous step. This key is validated behind the scenes by the ‘user admin API’ and the corresponding encryption key is returned. A random ‘state’ is generated and the ‘authorization url’ is constructed. The ‘state’ and ‘encryption key’ are then stored in the database behind the scenes by the ‘token storage API’. The ‘encryption key’ is encrypted with the original ‘state’ value and stored with a hashed version of the ‘state’ in the database. The constructed authorization url is then returned to the user.

The user then needs to be redirected or navigate to the URL in a browser and authorize the application.

Generating and storing access tokens:

Once the app has been authorized by the user, the service provider sends a request containing a ‘code’ parameter to a ‘redirect’ or ‘callback’ url. The ‘code’ is then exchanged along with other client identifiers with the authorization server and an access token is returned.

To achieve this in Linx, the Public API exposes a method to receive this callback request. The ‘state’ is validated via the ‘token storage API’ sub-service which returns the encryption key. The ‘code’ is then sent to the ‘oauth API’ sub-service to generate and return the access token. The returned access token is then encrypted using the value of the ‘encryption key’. The encrypted access token string is sent to the ‘token storage API’ and is stored in the database linked to the api key and service provider.

Retrieving access tokens:

To retrieve an access token to use in an external request, the user encryption key needs to be retrieved from the ‘user admin API’ by validating the submitted API key. The matching encrypted access token then needs to be retrieved from the ‘token storage API’. These two values are used in the decryption at runtime and the decrypted access token value is returned to the user.

Process flow

API Key registration:

  1. Users register a new API Key using their login credentials.
  2. The credentials submitted with the request are validated against the records in the database.
  3. A random ‘api key’ string is generated. 4.A random ‘encryption key’ string is generated. This will be used to encrypt any access tokens associated with the ‘api key’.
  4. The ‘api key’ is hashed.
  5. The ‘encryption key’ is encrypted using the plain text value of the ‘api key’.
  6. The hashed ‘api key’ and encrypted ‘encryption key’ are stored in the database.
  7. The plain text ‘api key’ string is returned to the user.

App authorization:

  1. User submits their API Key with the token retrieval request to the Public API.
  2. User's API Key is validated via the Linx ‘user admin api’ sub-service.
  3. A random string is generated as the ‘state’ parameter. This ‘state’ is included in the returned authorization url and stored in the database.
  4. The state is stored as a hashed string and the user’s encryption key is encrypted with the unhashed random string The user authorizes the app

Generating and storing access tokens:

  1. Authorization server executes a request to the Linx service callback endpoint.
  2. The state is validated against the hashed record in the db and the encrypted key is returned.
  3. The authorization code received in the request is sent to the internal Linx oauth service which exchanges the code for the access token with the service provider .
  4. The encrypted ‘encryption key’ is decrypted using the valid state.
  5. The decrypted encryption key’ is then used to encrypt the returned access token string.
  6. The encrypted access token string is sent to the ‘token storage service’ and is stored in the database linked to the key and service provider.

Retrieving access tokens:

  1. User submits their API Key with the request.
  2. The API Key is validated and the matching encryption key is returned
  3. The encrypted ‘access token’ is retrieved from the database.
  4. The encrypted ‘access token’ is then decrypted with the ‘encryption key’ returned from the authentication event.