fastCode provides a strong and fine-grained authentication and authorization model for securing enterprise applications.

Authentication

Authentication is the process of verifying who the user says he/she is. This verification may be done using a username and password supplied by the user, which are then compared with the username and password stored in the application’s database or an external provider such as an organization’s Active Directory.

In this current release, fastCode supports authentication using the application database or an external LDAP-compliant directory service such as Active Directory. In future releases, we will be supporting OAuth/OpenID Connect, and SAML.

When using an LDAP-based external directory service for authentication, you may choose to also store a user’s roles as LDAP Groups. Once you generate an application and run it for the first time, the following tables are created in the database.

  1. User – Contains just the user name field

  2. Role

  3. Permission

  4. User-Role Join Table

  5. User-Permission Join Table

  6. Role-Permission Join Table

Authorization

Once a user is authenticated, without authorization, the user has access to the full application and can perform any action. However, in the real-world, most applications use the concept of roles and permissions to restrict what actions the user can perform on the application. For example, if you have a customer table in your application’s database, one user may be able to create, update, and delete a customer, whereas another user may only be able to read the list of customers. Each of these create, update, delete, and read actions can be considered a separate permission.

A role is a group of permissions. The reason we have roles is for convenience - to group a set of permissions and apply a role to a user instead of assigning individual permissions to the user. Some applications also implement the concept of groups. A group is a set of users. You could assign a single permission or a role to a group, thereby assigning it to all the users in the group.

fastCode supports authorization using Role and Permission entities that can be assigned to a User. The relationships among these entities are as follows:

  1. User – Role: Many-to-Many. A User can have one or more Roles and a Role can be assigned to one or more Users.

  2. Role – Permission: Many-to-Many. A Role can have one or more Permissions, and a Permission can be assigned to one or more Roles.

  3. User – Permission: Many-to-Many. A User can directly be assigned one or more Permissions and a Permission can directly be assigned to one or more Users.

When two or more roles are assigned to the user, the assigned roles can have one or more common permissions. In such a case, only the unique permissions are assigned to the user.

A permission can also be assigned directly to a user without assigning a role to the user. When a permission is directly assigned to a user, the permission is added to the user, assuming that the permission is not already assigned to the user through a role.

A permission can also be assigned to the user using the revoke property set to true. In such a case, if the user is already assigned this permission either directly or through a role, this permission will be revoked from the user.

Jwt Tokens

Once a user is successfully authenticated, the application calculates the unique set of permissions that should be assigned to the user based on the role(s) assigned to the user and the permission(s) directly assigned to the user.

The application creates two Jwt tokens, one with the user’s authentication information (authentication token) and the other with the unique set of permissions assigned to the user (authorization token).

The authentication Jwt is stored in a httpOnly cookie with the secure flag turned on. On every request from the front-end to the back-end, this cookie is passed along in order to seamlessly authenticate the user. Storing authentication information in a httpOnly cookie prevents Cross-site Scripting (XSS) attacks.

The application front-end needs information about the user’s permissions in order to restrict what actions the user can perform from the UI, but if we store this information in the authentication Jwt token, the Angular framework will not be able to read the Jwt token and the associated permissions because the token is stored in a secure httpOnly cookie. Therefore, we are storing the user’s permissions as Jwt claims in a separate Jwt token (authorization token) that is sent back from the server to the client and stored in the client’s local storage.

Spring Boot enforces CSRF on any methods except GET, HEAD, and OPTIONS. In order to prevent Cross-site Request Forgery (XSRF) attacks, the Spring Boot back-end automatically creates a XSRF token, stores the token in a cookie, and sends it with the OPTIONS pre-flight request The client reads this cookie and passes it back to the server as a header (X-XSRF-TOKEN), along with the cookie. The server validates that the X-XSRF-TOKEN value in the header matches what's passed back as the XSRF cookie value and allows the request only if they match.

Jwt token storage in database

The generated application stores one or more pairs of authentication & authorization tokens for each user in the database.

In our current implementation, every time a user logs into the systems with a user name and password and is successfully authenticated, the application creates a pair of Jwt tokens – one for storing authentication information and the other for storing authorization information. As previously mentioned, the authentication token is stored in a httpOnly cookie and the authorization token is stored in a regular cookie.

If the user logins in again, for example, from a different browser, another paid of Jwt tokens will be created for the user. Therefore, it’s possible that multiple pairs of Jwt tokens may be associated with a single user.

Token Invalidation

There are scenarios when the Jwt authentication and authorization token pairs assigned to the user need to be deactivated or replaced. Such scenarios include the following:

  1. User deactivated. In this scenario, for example, the application administrator deactivates the user’s account. Therefore, the user with already issued and valid Jwt authentication tokens should not be able to login into the system.

  2. User’s set of permissions changes. In this scenario, for example, the application administrator changes the role(s) and/or permission(s) directly assigned to the user, effectively changing the user’s set of permissions. Therefore, the Jwt authorization tokens of this are no longer valid and a new pair of Jwt tokens should be issued to the user.

  3. The permissions assigned to a role change. In this scenario, for example, the application administrator changes the set of permissions assigned to a role. This would mean that the set of permissions for each user who is assigned this role also changes, invalidating the Jwt authorization tokens of these users. These users need to be assigned new Jwt token pairs.

We need to note that if we invalidate a Jwt authentication token in case of user deactivation, the corresponding Jwt authorization token also needs to be invalidated because if the user is re-activated at a later date, his permissions could be different and a new Jwt authorization token need to be issued along with the new Jwt authentication token.

Similarly, if a user’s Jwt authorization token is invalidated due to permission changes, the corresponding Jwt authentication needs to be invalidated as well because the user needs to re-authenticate in order to obtain a new Jwt authorization token.

There are two ways we can manage the pairs of Jwt tokens assigned to each user.

In the first approach, which is our current approach, we store the token pairs associated with each user in a database. When the user first authenticates with the application, a pair of Jwt tokens is issued. On subsequent requests, this pair of tokens is passed to the application’s back-end from the front-end. The back-end ensures that the Jwt authentication token is well-formed, has the right signature, and hasn’t expired. It then checks whether or not this Jwt authentication token exists in the database. If the token is well-formed, has the right signature, hasn’t expired, and exists in the database, the user is successfully authenticated. For authorization, the back-end authorizes the user based on his permissions stored as claims in the Jwt authorization token.

This first approach of managing user Jwt tokens is reliable, but not highly scalable. It is reliable because as soon as a user has been de-activated or a user’s effective permission set changes, the application can immediately prevent the user from logging into the system. It is not highly scalable because every request made from the client to the back-end involves a database lookup for authenticating and authorizing the user’s request.

An alternative approach to managing the Jwt tokens is to issue short-lived Jwt authentication tokens. In case we want to deactivate the user, if we have short-lived authentication and authorization tokens such as 15 minutes, this is the max time that a deactivated user can continue to use the system. Similarly, if the user’s permission(s) are changed, they won’t take effect for a maximum of 15 minutes. Before this 15-minute period, in order to not force the user to login in case their token are still valid, we need to refresh these two tokens.

This approach allows us to not have to look up the database for every user request to check whether or not the Jwt tokens are valid - this increases app scalability and preserves the reason for using Jwt tokens in the first place as a stateless security mechanism.

In a future release, we will during the application generation, we will allow users to choose their application security strategy - want immediate user deactivation with less scalability or within (X) minutes de-activation with higher scalability.

Enforcing API-level Permissions

fastCode provides granular access control at ReST API method level. The ReST API Method for entities are secured through Spring @PreAuthorize annotation. The generated application has a standard set of ReST operations for creating a new entity, updating an existing entity, deleting an entity, finding an entity by its id, and performing a Boolean search for entities. Each of these operations is annotated with a separate permission.

For example, if we take an Actor entity, the following annotations are used on the operations exposed by the ReST API for the Actor entity.

@PreAuthorize("hasAnyAuthority('ACTORENTITY_CREATE')")

@PreAuthorize("hasAnyAuthority('ACTORENTITY_UPDATE')")

@PreAuthorize("hasAnyAuthority('ACTORENTITY_DELETE')")

@PreAuthorize("hasAnyAuthority('ACTORENTITY_READ')")

In general, the format for each annotation is @PreAuthorize("hasAnyAuthority('{entityName}ENTITY_[CREATE, UPDATE, DELETE,
READ]')").

Here, 'ACTORENTITY_CREATE', 'ACTORENTITY_UPDATE', 'ACTORENTITY_DELETE', 'ACTORENTITY_READ' are permissions. These permissions are then assigned to one or more Roles or are directly assigned to a User.

Therefore, for example, if a user tries to execute the create method of the Actor entity, and the user is not assigned the ACTORENTITY_CREATE permission, an error will be returned back to the client.