How to store Msal node token cache in database and Stop clearing cache on app restart

Solution for How to store Msal node token cache in database and Stop clearing cache on app restart
is Given Below:

I am calling graph apis from my node app. For that i have create confidential client application. I want to store token cache in database(postgres). How can i do that?

Also whenever i restart my node app, msal clearing its token cache, is there any way to stop msal node clearing its cache on app restart?

Current implementation as below:

const fs = require('fs');
const msal = require('@azure/msal-node');
const cachePath="cache.json";
const beforeCacheAccess = async(cacheContext) => {
    cacheContext.tokenCache.deserialize(fs.readFile(cachePath, "utf-8", (err, d) => {
        //console.log("errror", err, d)

    }));
};

const afterCacheAccess = async(cacheContext) => {
    if (cacheContext.cacheHasChanged) {
        fs.writeFile(cachePath, cacheContext.tokenCache.serialize(), (err) => {
            //console.log("errror", err)
        });
    }
};

// Cache Plugin
const cachePlugin = {
    beforeCacheAccess,
    afterCacheAccess
};

const msalConfig = {
    auth: {
        clientId: config.MS_CLIENT_ID,
        authority: config.AAD_ENDPOINT + 'consumers', //config.TENANT_ID,
        clientSecret: config.MS_CLIENT_SECRET,
    },
    cache: {
        cachePlugin // your implementation of cache plugin
    },
};
const scopes = ["Mail.ReadWrite", "Mail.Send"]

const cca = new msal.ConfidentialClientApplication(msalConfig);

Exact implementation depends on what database you want to use, but roughly sketched you would have to implement calls to the database instead of the file system in your example:

...
const beforeCacheAccess = async(cacheContext) => {

    let dataFromDb = someMethodThatReadsTheCacheFromDb()

    if(dataFromDb == null) {
      // didn't find previously existing data, so store current cache regardless if empty or not
      someMethodThatStoresCacheToDb(cacheContext.tokenCache.serialize())
    } else {
      // found cache data, restore into the cache context
      cacheContext.tokenCache.deserialize(dataFromDb)
    }
};

const afterCacheAccess = async(cacheContext) => {
    if (cacheContext.cacheHasChanged) {
        // store changes to db
        someMethodThatStoresCacheToDb(cacheContext.tokenCache.serialize())
    }
};
...

Note however that this does not scale very well, because the current cache context would store the tokens for ALL clients into the same serialized string.

According to this discussion comment, there is a roadmap item for standardising plugging in a distributed cache, but atm. no official implementation exists.
https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/2828#issuecomment-797696193

Also recommend reading through this discussion (not node.js specific, but relates to token caching in MSAL):
https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/98, specifically this comment:
https://github.com/AzureAD/microsoft-authentication-library-for-python/issues/98#issuecomment-535754922

It explains that the cache system in MSAL was originally built with the intention of client apps, not web apps, which would mean a limited nr of accounts would need to be stored in the cache, as the intention was for the cache to run on e.g. user phones. So the cache does not scale well, and instead if you need to support many users you should create a cache per user and then use the user specific cache for each request. E.g. serialize the cache and store it in the database with a user specific key that you can use to fetch and restore the cache on later requests.

As for the cache getting cleared during application startup I don’t know. I assume if the cache is written to the filesystem like your example plugin implementation suggests, then it should be restored from there when msal tries to access the cache the first time. So I’d start by verifying that the cache is actually getting written to your cachePath. If for some reason your cachePath gets reset when you start the app (e.g. if you happen to use webpack to run your code and the cache is stored in a build folder that gets deleted on each build), then that could also explain the reset.