Authentication Context Cache Optimization : How it is done

Thananchseyan
12 min readAug 9, 2022

--

If you ever used WSO2 Identity Server, then you probably came across the word called “Authentication Context”. We are using it for the authentication process. If you wonder how we are using the authentication context cache and other caches during the WSO2 Identity Server’s authentication process, take a look at this.

If you are interested about authentication context, this .

As I mentioned in the above blogs we are going to store the session objects in the database by serializing them and storing them as BLOB data types. Therefore, it will be good if we are able to optimize them before we store them.

First we’ll take a look at the need to optimize the session objects and how we optimized the authentication context cache here. Before moving into the optimization, Let’s take a small recap about the caches and how we are using them.

OIDC code flow in WSO2 Identity Server

When we consider WSO2 Identity Server, There are two components of the WSO2 Identity Server that take part in authentication which are OAuth component and the Framework component when the flow is OAuth/OIDC. The OAuth component is responsible for maintaining the flow protocols and the Framework component is responsible for the user authentication process. When we consider the database, We are using two tables in the database for storing the authentication process information. Those are IDN_AUTH_TEMP_SESSION_STORE and IDN_AUTH_SESSION_STORE. When the control of the process is handed over to the user, the server will store the current status of the process as a temporary placement. These data are stored in IDN_AUTH_TEMP_SESSION_STORE. For example, If we have a Multi-Factor Authentication, we have to store the results of the first step before the second step of the authentication then, server store the results of the first step in the IDN_AUTH_TEMP_SESSION_STORE and sends back the response to the user. Other data types that are related to the session are stored in IDN_AUTH_SESSION_STORE.

In the OIDC authorization code flow, the following session types are persisted. In the IDN_AUTH_TEMP_SESSION_STORE table

  • OAuthSessionDataCache
  • AuthenticationContextCache
  • AuthenticationResultCache are found.

In IDN_AUTH_SESSION_STORE table

  • AppAuthFrameworkSessionContextCache
  • OIDCSessionParticipantCache
  • AuthorizationGrantCache sessions are found.

Authentication Context Cache, which is a temporary session data type that includes necessary fields for the authentication process, is stored in a temporary store. Authentication Context Cache is obtained by using the Session Data Key from CommonAuth and the session is validated and values are set and stored. There are several attributes in the Authentication cache and they hold specific usage in storing user-related information.

We are using java objects which are used by the server to be serialized and stored in these tables as BLOB data types. Usually, these objects are heavy; therefore, storing and retrieving this data in the database adds load to the database. This leads to major performance bottlenecks in the authentication flow. When we consider the temporary session data types, the size of the Authentication Context Cache is large. Therefore we decided to optimize the authentication context cache first. The following descriptions about the BLOB size of the authentication context cache will help you to understand the need for optimization. When we are using two steps in the MFA, Most of the time we store the authentication context cache two times, first when the system control is handed over to the user for the first authentication step and then after the first step. We got the BLOB sizes which were obtained before the 1st step, after the 1st step and analyzed. When we tried the MFA, the average value obtained before Step 1 is 28971.8 bytes and it is 20977 bytes after the first step. When we tried the flow with the federated identity provider, We stored the authentication context cache before the first authentication step and got the average value as 19291 bytes. And when we tried the flow with the local authenticator, We got 29831.4 as an average value for the authentication context cache object which is stored before the first authentication step. And if there are any missing claims after the authentication steps then there will be another storing process of authentication context cache but that won’t happen often.

When we analyzed the authentication context object and its attributes, we found some facts which have some considerable space in the authentication context’s weight. It may result in major performance bottlenecks. Therefore ideas for optimization are suggested. They are stated below. First, we have server data which is stored per context. We have some set of data that is defined in the server which are also stored in the session objects. Those data cannot be changed in the runtime. Therefore they don’t not need to be stored in the session object. They can be optimized by persisting only the reference of them instead of the entire object. When there is a need for the actual object, it can be obtained from the server config. Second, we can observe SP and IDP config stored per context. We found that we have identity provider and service provider objects which are also defined in the server. When we increase the authentication steps, the identity provider object will be stored in StepConfig of the SequenceConfig and in AuthenticatedIDPData of the CurrentAuthenticatedIDP. It will increase the amount of space. Therefore instead of persisting the complete object, we can persist only the reference for the object and then get them from the database. Considering the possibilities of duplicate entries, we found that certain values are being repeated in a few distinct places where we can try to optimize them.

Other than these facts, we had some considerations based on the optimization of the authentication context. Considering the observations we had, we decided to optimize the Identity Provider Configs as the first step which will lead to a high gain in storage optimization. And then before starting the optimization of the service provider, We needed to evaluate the Service Provider config in a case where we can change the configuration of the service provider in the run time. Therefore, there was a need to do the evaluation on the gain if we try to optimize the SP configuration and also needed to check what are the properties which won’t affect the flow even if we remove it from SP Config. It was said that valuation should be done on the gains that can be obtained by retrieving from the database instead of storing them in the session objects. If we are going to read SP config from the database then we need to check the authentication flow because if we lose the flow in the middle then the user needs to restart the authentication flow. We needed to do an analysis on these things and evaluate the gain in each case. Based on the analysis it was needed to identify the most suitable optimization approach. Also, we needed to keep the backward compatibility of the Authentication Context public methods as they are used in the custom authenticators. Therefore we remove the attributes but keep the methods as they are.

When we consider the optimization, we cannot just remove the bulk objects from respective classes and replace them with the reference for them in classes. If we are going to follow that approach, then we will change the framework and it will lead to change in many public methods and when we have to handle the exceptions it will lead us to change the method signature as we have to throw the exceptions in method signatures and there will be a hierarchy of the affected methods. But we have to keep backward compatibility. Therefore we approached the loader class mechanism. We introduced a new class called Authentication Context Loader which is used to optimize the authentication context object and load the optimized authentication context object.

We are going to optimizeAuthenticationContext() method before we persist the object using storeSessionData() of the Session Data Store class. We added the configuration to disable the optimization process. If you don’t want to do the optimization during the authentication flow then you can disable the flow by changing the configuration in the deployment.toml.

To disable the optimization process

  1. Locate the deployment.toml in Product-IS (Product-IS\repository\conf\deployment.toml)
  2. Add the following configuration in deployment.toml

[session_data.storage_optimization]

enable_context_optimization = false

We will check the configuration if session data storage optimization is enabled or not. If it is enabled then we will pass the authentication context object to the optimization process to the authentication context loader class. We introduced a new exception called Authentication Context Loader exception to handle the exception during the optimization and loading processes. If any exception occurred during the optimization process then the authentication context object would have been partially optimized. Therefore we decided not to follow the flow then. Exceptions will occur if the loader cannot find the objects using the reference.

After we persist the object, We again load the optimized authentication context object. Because we are optimizing the same authentication context object, therefore the object in the cache will also optimize. Therefore we can immediately load the optimized authentication context.

When we get the authentication context object by using getValueFromCache (AuthenticationContextCacheKey key), it will get the object from the cache if there is an object or else it will try to get it from the database. If it is the particular object then we don’t need to load it again, if it is from the database then we should load the optimized authentication context. Here there is a possibility to change the configuration about the optimization in the run time, therefore we had to pass the object to the loading process. We will check if the object is optimized or not during the loading process. If it is an optimized object then we will load it or else we will just return it.

If there is any exception occurred during the loading process then the authentication context will not be a completely loaded object. If we pass it to the framework then there will be many issues to ensure that if we face any exceptions during the loading process, we will return the entry as the null object. The Framework handles the remaining flow by considering the entry as null.

As we mentioned above we have server level data that is stored per context and service provider objects and identity provider objects that are stored in context as an entire object. We have identity provider objects in authenticator config in the idps that have list of identity provider objects. Authenticator config is placed in many distinct places. It is in step config as authenticated authenticator, list of authenticator configs as authenticator list in step config and in the authenticated idp data. The number of identity provider objects will increase with the number of steps in the authentication flow. And We have the entire service provider object in the application config of the sequence config.

In the Authentication Context Loader class, We have an optimizeAuthenticationContext( AuthenticationContext context) method to optimize the authentication context object. We are optimizing the authentication context in 3 categories. We optimize the external IdP (Identity Provider) which is an attribute of the authentication context class, optimization of the authenticator configs from step configs and optimization of the application config of the sequence config.

In the same manner, we will load the optimized authentication context object by using loadAuthenticationContext(AuthenticationContext context) of the authentication context loader class.

For the optimization of the External IdP, We introduced a new attribute called externalIdPResourceId which is used to store the reference number of the identity provider that we are using for construction of the external idp config. We will get the reference number of the identity provider during the optimization and set it in the externalIdPResourceId and set the external idp object as a null object. We are going to get the identity provider from the database by using its reference id. And we can get the external IdP config by constructing it using the identity provider.

In the step configuration, we have a list of authenticator configs as authenticator list and after the authentication of the specific step configuration, we will get the respective authenticator config and set it as the authenticated authenticator. So during the optimization, we are going to just store the name of the authenticator config instead of the entire authenticator config in the authenticated authenticator. To do that, we introduced a new attribute called authenticatedAuthenticatorName. We can get it using the getAppAuthenticatorByName() method of the FrameworkUtils class during the loading process. We are going to iterate the step map in the sequence config to get every authenticator configs in step configs and optimize them.

In the authenticator config, we have application authenticator which is the server level data that can be obtained later. Therefore during the optimization, we are going to set the application authenticator as null. And instead of having the map of identity providers with their name as a key and a list of identity provider names we are going to get the reference ids of them and store them as a list. To do that we introduced a new attribute called idpResourceIds in the authenticator config class. And during the optimization process, we are going to set the idps and idpNames as null. We can get the identity providers from the database using their reference ids.

There was a concern about the optimization of the service provider. The configuration of the service provider can be changed during the run time. In that case, if we get the service provider object from the database, then it will be with the modified configuration. It will affect the flow. To ensure that flow won’t break we had to keep some attributes in context. Therefore we created a new class called Optimized Application Config. We are going to have the resource id of the service provider, authentication steps of the service provider, claims and role mappings. The Authentication Step class has a list of local authenticator configs which are server level data and an array of identity providers as federated identity providers which are also server level data. Therefore we created a private class in the optimized application config class to have optimized authentication steps with the references for the local authenticator configs and federated identity providers.

We are going to construct the Optimized Application config class using the application config class. And We are going to get the service provider from the database using its resource id. And to ensure that flow won’t be affected in the above-mentioned case, We are going to reconstruct the service provider using the attributes which we had in the optimized application config object.

There are some service providers which are file-based applications. Those are not from the database, therefore they don’t have a resource id. Therefore we modified it to set the application name as the resource id only for the file-based applications. So the Application Management Service class which is used to get the application using resource id will check the cache first. If there is no application with that resource id then it will look in the database, if it is not in both cache and database and then it will check the file-based applications.

Let’s have a look at the reduction percentages which we got after the optimization process. The reduction percentage of the BLOB sizes is expressed by the following formula.

Reduction % ={(Average Initial blob size — Average Size of the blob after the optimizations)/ Average initial blob size} * 100

It is the percentage of difference between Average Initial blob size and Average Size of the blob after the optimizations to the Average initial blob size. When we tried the flow in the two step MFA, we got 75.4657% of reduction percentage from the authentication context cache which is stored before the first authentication step and 59.4708% of percentage in the next object. When we tried the flow with a federated identity provider, We got a 62.568% reduction percentage. When we tried the flow with the local authenticator, we got 76.92% of the reduction percentage.

Overall let us view the reduction percentage of BLOB sizes after completing the above-mentioned optimization process. Before the 1st authentication step the reduction percentage of BLOB sizes using the Local Authenticator (LA) is 75% to 80% and using Federated IdP(FIdP) is 60% to 65% and using both LA and FIdP is 75% to 80%. After the 1st authentication step the reduction percentages of BLOB sizes are 40 to 45% for using LA and greater than 50% — 60% for using FIdP. If we get the authentication context cache after the authentication steps, the reduction percentage will be greater than 50%.

I hope, This would conclude the overall view of the Optimization in the authentication context, you could have gained the knowledge of how the optimization is done and the process included in it.

--

--