# Persistent database in localStorage

In the previous step, we added a password pre-derivation step on the authentication method to ensure the security of password protection of a user's Seald identity.

We are still facing the problem that when a user opens a new tab in the application, despite having an authenticated session, the user has to retype their password to recover their Seald identity.

In this step, we will add an identity cache in localStorage so that the user can open a new tab.

The branch on which this step is based is 2-pre-derivation (opens new window), the final result is 3-localstorage (opens new window).

# Explication

In the mechanism implemented in the previous steps, the Seald identity of a user is retrieved from SSKS and then decrypted using the user's password. Thus, this identity is only kept in memory on the client side.

It is however desirable that when a user refreshes the page, or opens a new tab, they have access to their Seald identity without retyping a password.

The solution we recommend is to cache the database in the browser in the localStorage, and as a precaution, we recommend to protect this database in localStorage by a key stored in the backend called databaseKey.

TIP

The Seald database uses nedb (opens new window), which in the browser does not necessarily use the localStorage, but choses (through localForage (opens new window)) the best storage method between IndexedDB, WebSQL or localStorage depending on your browser.

uml diagram

When the user reopens the application, the databaseKey will be retrieved from the backend via an authenticated session, and this key will be used to decrypt the encryptedDB.

uml diagram

All this is done without any user interaction.

# Storing the database

To store the database, we will modify the createIdentity function to save the database created in localStorage in encrypted format.

To do this, we add a databasePath argument to store the database, and a databaseKey argument to encrypt it.

TIP

This example choses to use a different databaseKey per session. For a simpler implementation, you can instead opt to use a single databaseKey per user for all their sessions.

TIP

We add sessionID in the databasePath in order for it to be unique per-session.

Indeed, if a user logs-out, then logs-in again, the server will have generated a new databaseKey. Consequently, we cannot use the same database: the database needs to be unique for each session, so we make the databasePath change with sessionID.

When we call the createIdentity function, the identity is both saved on SSKS by the password known by the user, and in localStorage protected with the databaseKey.

Similarly, when we call retrieveIdentity, the identity retrieved from SSKS is stored in localStorage encrypted with the databaseKey.

We will see later how to manage this databaseKey.

TIP

It is possible to create a different sub-identity for password protection and for protecting the local database.

Here, for simplicity, the same identity will be in the local database and protected by password.

# Identity retrieval

To retrieve the identity from the localStorage, we will create a function retrieveIdentityFromLocalStorage which instantiates the SDK and checks that the database is in the expected state:

When calling the retrieveIdentityFromLocalStorage function, the database is retrieved from the localStorage, decrypted with the databaseKey and loaded in the SDK. We will see later how to manage this databaseKey.

# Managing the databaseKey

The databaseKey is a secret that protects the database stored in the localStorage, including the user's identity. It must be recoverable by the user in an authenticated way.

We will generate it from the backend, store it in session, and modify the frontend API client accordingly.

TIP

For faster instantiation, you can use a databaseRawKey instead of the databaseKey. For more details, see the "Using a persistent local database" section of the Identity Guide.

# Modification of the API in the backend

To do this, we will modify the following routes in the backend/routes/account.js file:

  • account creation: POST /account/ to generate, store and return the databaseKey & sessionID;
  • login: POST /account/login to generate, store and return the databaseKey & sessionID;
  • logout: GET /account/logout to delete the databaseKey;
  • status: GET /account/ to return the databaseKey & sessionID.

Before modifying the routes, you have to import the crypto module to generate random databaseKey with function :

In the account creation and login routes, the databaseKey is generated and returned:

In the status route, we return the databaseKey:

In the logout route, we remove the databaseKey from the session:

# Modification of the API client in the frontend

We will reflect these changes in the API client frontend/services/api.js.

To do this we need to:

  • store this databaseKey & sessionID as a properties of the User class
  • retrieve them after the API calls in the static methods User.createAccount, User.login and User.updateCurrentUser.

# Retrieving from localStorage at initialization

The last step is to call retrieveIdentityFromLocalStorage at application load in the init function of frontend/App.js.

We need to:

  • attempt a GET /account/ call to check if the browser has an authenticated session, and retrieve the databaseKey & sessionID;
  • try to retrieve and decrypt the database stored in localStorage.

If there is an error on either of these two steps, we set the currentUser to null which will take the user to the login step.

# Conclusion

The identity is now cached, so a logged-in user can close the chat window and reopen it without having to retype their password.

There is still one last step for a satisfactory result: generate the license token on the backend.