Access Control Overview

Overview

Access to application resources (entities) is controlled in four ways:

  1. Configuration based service activation.
  2. Secure user authentication.
  3. Authorization checks via JWT token claim inspection embedded as middleware in the protected route declarations (end-points).
  4. Usr/Auth/Group/GroupAuth caches are maintained on each group-member and updated via an internal API and dissemination protocol in the group-membership service.

Configuration Based Service Activation

An internal service is created for each of the the application’s entities. Services can be marked as active or inactive in the service configuration, thereby allowing a single application to be generated, while at the same time allowing selective service deployment. For example, there may be cases where it is desirable to route certain services to a particular application instance and another set of services to the rest of the pool. In such a case, a reverse-proxy could be configured to route the end-points appropriately, and the deployed service configurations would be adjusted accordingly. Service activations are controlled via entries in the ‘service_activations’ block of each application instance’s configuration file.

The following configuration file excerpt shows that the selected instance of the application is servicing ‘Person’ entity requests, but is not presently servicing ‘Car’ entity requests. However, if a relationship exists between ‘Person’ and ‘Car’, requests rooted on the ‘Person’ entity but involving the ‘Car’ entity will be accepted. For example, the selected instance would not honor a GET request for ../car/1234, but would accept and process a request for ../person/1234/toCar. In practice, the reverse-proxy/load-balancer should be configured to route requests to the correct end-point.

...
...,
    "service_activations": [
        {
            "service_name":   "Person",
            "service_active": true
        },
        {
            "service_name": "Car",
            "service_active": false
        }
        ]
}

Secure User Authentication

User authentication is conducted using bcrypt in such a manner that passwords are never stored in the application database. When a user is created, their user-id is stored in the database along with the salt/peppered bcrypt hash of their password. This ensures that in the event of a breach no plain-text passwords can be obtained.

Bcrypt was chosen for the following reasons:

  • Bcrypt hashes are salt/peppered to mitigate rainbow table attacks.
  • Bcrypt is slow by design, making brute force reversal a time-consuming and expensive proposition.
  • As increased computing power becomes available, the bcrypt cost parameter can be increased (current = 14).
  • The password hash itself is not used for authentication; it is the by-product of successful authentication.

When a user logs into the application the following steps occur:

  • A lookup of the user-name and stored bcrypt hash is executed against the database.
  • The user provided password is hashed in memory using the Go standard lib bcrypt functions and the protected salt/pepper values.
  • The computed bcrypt hash is compared to the stored hash value for the user.
  • If the hash values match, a JWT (token) is created and signed using ECDSA-256 (adjustable to ECDSA-384, ECDSA-521, RSA-256, RSA-384, RSA-512).
  • The JWT is passed back to the caller and must henceforth be included in the http header of all requests using the Authorization field.
  • The users access credentials are updated in the servicing group-member’s cache.
  • The users access credentials are passed via the cache subsystem to all other group-members.

In addition to fulfilling the authorization requirements, the JWT is also used as a CSRF equivalent. By default the generated JWT has a validity of one-hour, but this may be adjusted via the application configuration file. See the following Authorization and End-Point Security section for more details regarding the content and use of the JWT content/claims.

Authorizations & End-Point Security

In addition to password authentication, generated applications provide the ability to manage access to their end-points via an authorization scheme. At a high-level:

  • An Authorization is generated and linked to each end-point.
  • Authorizations are assigned to User Groups.
  • User Groups are allocated to Users via a Groups field in the Usr master.
-> User
   |
   --> Group 1
   |  |
   |  --> Auth_EndPoint_A
   |  --> Auth_EndPoint_B
   |  --> Auth_EndPoint_C
   |
   --> Group 2
      |
      --> Auth_EndPoint_K
      --> Auth_EndPoint_M

Authorizations

Application access can be restricted at the end-point level. Each generated end-point is given a name based on its entity, http method and purpose. The gorilla mux provides an easy way to assign names to end-points in a route declaration, and these names are defined in the generated application as Authorizations or Auths.

Authorizations are created per end-point and are therefore known to the router, which in turn allows the route middleware of the generated application to determine which Authorization is needed in order to permit the request to proceed. Recall that an authenticated user is sent a signed base64-encoded JWT token that must be passed in the http header Authorization field of each request. The generated router middleware validates the signature, decodes the token, and then examines its Claims in order to determine whether the request should be allowed to proceed. This level of checking can be thought of as the Authentication verification; does the requesting party have a valid access token for the system in general?

Assuming that the requesting user has a valid access token, the next step is to determine whether the user has permission to access the requested end-point. Each User is assigned to one or more User Groups and these are included as a Groups Claim in the JWT token when the User logs into the application. As a result, the route middleware is able to examine the content of the Groups Claim in order to determine whether the User is permitted to access the requested end-point. The route authorization check unfolds as follows:

  • Verify the requesting user has a valid access token (JWT); can the signature be verified? Is the JWT still valid?
  • Read the Groups Claim from the JWT token.
  • Determine the ‘Name’ (Authorization) of the current route.
  • Examine the authorization cache for each Group the User has been assigned to.
  • If the required Authorization is found in any of the User Groups that the User has been assigned to, the request is allowed to proceed.

The last bullet point is interesting, as it means that end-point access of protected routes is denied by default. Unless access is specifically granted via Authorization -> User Group -> User assignment, the protected end-point is not accessible.