question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Implementation of persistent token cache for msal-node inconsistent and incomplete - basic JSON file implementation not working as expected

See original GitHub issue

Core Library

MSAL Node (@azure/msal-node)

Core Library Version

1.3.3

Wrapper Library

MSAL Node Extensions (@azure/msal-node-extensions)

Wrapper Library Version

1.0.0-alpha.12

Description

There is no clear documentation provided to achieve the following use case for msal-node. The linked documentation is for dotnet. And from what I can gather there are a number of competing documents or resources on how to access token cache for the purpose of “allow background apps, APIs, and services to use the access token cache to continue to act on behalf of users in their absence.”.

It would be great to understand what we should or should not be implementing in order to achieve the behaviour below:

https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-acquire-cache-tokens#advanced-accessing-the-users-cached-tokens-in-background-apps-and-services

(Advanced) Accessing the user’s cached tokens in background apps and services

**You can use MSAL’s token cache implementation to allow background apps, APIs, and services to use the access token cache to continue to act on behalf of users in their absence. Doing so is especially useful if the background apps and services need to continue to work on behalf of the user after the user has exited the front-end web app.

Today, most background processes use application permissions when they need to work with a user’s data without them being present to authenticate or reauthenticate. Because application permissions often require admin consent, which requires elevation of privilege, unnecessary friction is encountered as the developer didn’t intend to obtain permission beyond that which the user originally consented to for their app.**

This code sample on GitHub shows how to avoid this unneeded friction by accessing MSAL’s token cache from background apps:

Accessing the logged-in user’s token cache from background apps, APIs, and services

What I have managed to find

Error Message

token cache getAllAccounts() method returns empty array

Msal Logs

[Sun, 14 Nov 2021 06:29:19 GMT] : @azure/msal-node@1.3.3 : Info - getTokenCache called TokenCache { cacheHasChanged: false, storage: NodeStorage { clientId: ‘d1e92154-378c-4784-8214-acd958eedd1d’, cryptoImpl: CryptoProvider { pkceGenerator: PkceGenerator {} }, cache: {}, changeEmitters: [ [Function: bound handleChangeEvent] ], logger: Logger { level: 3, localCallback: [Function: loggerCallback], piiLoggingEnabled: false, correlationId: ‘’, packageName: ‘@azure/msal-node’, packageVersion: ‘1.3.3’ } }, persistence: { beforeCacheAccess: [AsyncFunction: beforeCacheAccess], afterCacheAccess: [AsyncFunction: afterCacheAccess] }, logger: Logger { level: 3, localCallback: [Function: loggerCallback], piiLoggingEnabled: false, correlationId: ‘’, packageName: ‘@azure/msal-node’, packageVersion: ‘1.3.3’ } } TokenCacheContext { cache: TokenCache { cacheHasChanged: false, storage: NodeStorage { clientId: ‘d1e92154-378c-4784-8214-acd958eedd1d’, cryptoImpl: [CryptoProvider], cache: {}, changeEmitters: [Array], logger: [Logger] }, persistence: { beforeCacheAccess: [AsyncFunction: beforeCacheAccess], afterCacheAccess: [AsyncFunction: afterCacheAccess] }, logger: Logger { level: 3, localCallback: [Function: loggerCallback], piiLoggingEnabled: false, correlationId: ‘’, packageName: ‘@azure/msal-node’, packageVersion: ‘1.3.3’ }, cacheSnapshot: undefined }, hasChanged: false } [] [Sun, 14 Nov 2021 06:29:19 GMT] : @azure/msal-node@1.3.3 : Verbose - initializeRequestScopes called

MSAL Configuration

const cachePath = "test.json"

// Call back APIs which automatically write and read into a .json file - example implementation
const beforeCacheAccess = async (cacheContext) => {
  cacheContext.tokenCache.deserialize(fs.readFile(cachePath, "utf-8"));
};

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

const cachePlugin = {
  beforeCacheAccess,
  afterCacheAccess,
}

const config = {
  auth: {
      clientId: "XXX",
      authority: "https://login.microsoftonline.com/XXX",
      clientSecret: "XXX"
  },
  cache: {
    cachePlugin
  },
  system: {
      loggerOptions: {
          loggerCallback(loglevel, message, containsPii) {
              console.log(message);
          },
          piiLoggingEnabled: false,
          logLevel: msal.LogLevel.Verbose,
      }
  },
};

Relevant Code Snippets

const AWS = require("aws-sdk");
const express = require("express");
const serverless = require("serverless-http");
const msal = require('@azure/msal-node');
const fs = require('fs');

const cachePath = "test.json"

// Call back APIs which automatically write and read into a .json file - example implementation
const beforeCacheAccess = async (cacheContext) => {
  cacheContext.tokenCache.deserialize(fs.readFile(cachePath, "utf-8"));
};

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

const cachePlugin = {
  beforeCacheAccess,
  afterCacheAccess,
}

const config = {
  auth: {
      clientId: "XXX",
      authority: "https://login.microsoftonline.com/XXX",
      clientSecret: "XXX"
  },
  cache: {
    cachePlugin
  },
  system: {
      loggerOptions: {
          loggerCallback(loglevel, message, containsPii) {
              console.log(message);
          },
          piiLoggingEnabled: false,
          logLevel: msal.LogLevel.Verbose,
      }
  },
};

const scopes = ["Calendars.Read.Shared", "Calendars.ReadWrite.Shared"]

const app = express();

const cca = new msal.ConfidentialClientApplication(config);
const msalCacheManager = cca.getTokenCache();

app.use(express.json());

app.get("/availability", async function (req, res) {

  console.log(msalCacheManager);
  // get Accounts
  const accounts = await msalCacheManager.getAllAccounts();

  console.log(accounts);

  // Build silent request
  const silentRequest = {
      account: accounts[0], // You would filter accounts to get the account you want to get tokens for
      scopes: scopes,
  };

  // Acquire Token Silently to be used in MS Graph call
  cca.acquireTokenSilent(silentRequest).then((response) => {
      console.log("\nSuccessful silent token acquisition:\nResponse: \n:", response);
      res.json({ response });
      // return msalCacheManager.writeToPersistence();
  }).catch((error) => {
    res.status(500).json({ error });
  });

 
});

app.get('/authenticate', async function (req, res) {
  const authCodeUrlParameters = {
      scopes,
      redirectUri: "http://localhost:3000/dev/redirect",
  };

  // get url to sign user in and consent to scopes needed for application
  cca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
      res.redirect(response);
  }).catch((error) => {
    console.log(JSON.stringify(error));
    res.status(500).json({ error });
  });
});

app.get('/redirect', async function (req, res) {
  const tokenRequest = {
      code: req.query.code,
      scopes,
      redirectUri: "http://localhost:3000/dev/redirect",
  };

  cca.acquireTokenByCode(tokenRequest).then(async function (response) {
      console.log("\nResponse: \n:", response);

      try {
        res.json({ response });
      } catch (error) {
        console.log(error);
        res.status(500).json({ error: "Could not create user" });
      }
  }).catch((error) => {
      console.log(error);
      res.status(500).send(error);
  });
});

app.use((req, res, next) => {
  return res.status(404).json({
    error: "Not Found",
  });
});


module.exports.handler = serverless(app);

Reproduction Steps

  1. Start the app
  2. Authenticate
  3. test.json will have the data written to the json file
  4. Visit the /availability location
  5. The silent auth method fails

Expected Behavior

The app should be able to use the token cache stored in the json file

Identity Provider

Azure AD / MSA

Browsers Affected (Select all that apply)

None (Server)

Regression

No response

Source

External (Customer)

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
samuelkubaicommented, Nov 18, 2021

Yes, @dillonbailey, in order to work with a JSON file the stated document will provide the correct approach.

We will work on updating the doc on configuration as well, thank you for that.

0reactions
dillonbaileycommented, Nov 18, 2021

Amazing, thanks @samuelkubai

Will reopen if any issues. Also I never replied to your earlier question on environment. This will be deployed to a server less environment on AWS.

Thanks again!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Implementation of persistent token cache for msal-node ...
Implementation of persistent token cache for msal-node inconsistent and incomplete - basic JSON file implementation not working as expected.
Read more >
microsoft-authentication-library-for-js/cache.json at dev - GitHub
microsoft-authentication-library-for-js/lib/msal-node/cache.json. Go to file ... "uid.utid-login.microsoftonline.com-refreshtoken-mock_client_id--": {.
Read more >
Persist Token Cache or Better Solution - Microsoft Q&A
Here's the problem: every time I want to push any updated to prod the cache of tokens are lost due to being held...
Read more >
Ultimate Guide to JSON Parsing with Swift - Ben Scheirman
This is a guide that will take you through almost any scenario you might encounter when translating JSON representations to your Swift types....
Read more >
MySQL 8.0 Reference Manual :: 11.5 The JSON Data Type
Partial Updates of JSON Values. In MySQL 8.0, the optimizer can perform a partial, in-place update of a JSON column instead of removing...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found