Search code examples
node.jsoutlookazure-active-directorymicrosoft-graph-apiazure-ad-msal

Handling refresh tokens in Azure (Microsoft graph) delegation flow


I am working on a project where I need to create events in user's Outlook calendars. The requirement is that job inspection dates should be added to the calendar of relevant users. Additionally, users should have the ability to manually create events on their calendars.

What I have imlemented:

  1. User will be redirected to microsoft login page.
  2. After login Microsoft will give us access token for that user.
  3. Using getTokenCache we are getting refresh token for that user.

Here is the code:

Routes:

router.get('/login', getAuthCodeUrl);
router.get('/callback', handleCallback);

Controllers:

const { ConfidentialClientApplication } = require('@azure/msal-node');

// TEST APP
const clientId = '945bf51b-xxxx-c5a83898b4b8';
const clientSecret = '9mV8Q~xxxx.zf9GqLGt95UUJ_bGdcp';

const msalConfig = {
  auth: {
    clientId: clientId,
    authority: `https://login.microsoftonline.com/common`,
    clientSecret: clientSecret,
  },
};

const redirectUri = 'http://localhost:3000/api/callback';

const scopes = [
  'User.Read',
  'Calendars.ReadWrite',
  'offline_access',
  'openid',
  'profile',
];

const cca = new ConfidentialClientApplication(msalConfig);

const getAuthCodeUrl = async (req, res) => {
  const authCodeUrlParameters = {
    scopes,
    redirectUri,
  };

  const authUrl = await cca.getAuthCodeUrl(authCodeUrlParameters);
  console.log('authUrl: ', authUrl);
  res.redirect(authUrl);
};

const handleCallback = async (req, res) => {
  const tokenRequest = {
    scopes,
    code: req.query.code,
    redirectUri,
    accessType: 'offline',
  };

  try {
    const authResult = await cca.acquireTokenByCode(tokenRequest);

    const accessToken = authResult.accessToken;

    const refreshToken = () => {
      const tokenCache = cca.getTokenCache().serialize();
      const refreshTokenObject = JSON.parse(tokenCache).RefreshToken;
      const refreshToken =
        refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;
      return refreshToken;
    };

    const tokens = {
      accessToken,
      refreshToken: refreshToken(),
    };

    console.log('tokens: ', tokens);

    // Handle token result, store tokens, etc.
    res.send('Authentication successful. You can close this window.');
  } catch (error) {
    console.error('Error obtaining access token:', error);
    res.status(500).send('Error obtaining access token');
  }
};

Here are my questions:

  1. In our refreshToken function, we are not passing any thing related to this user. I am confused how MSAL gets refresh tokens for this user and not any other?
  2. "access token can only be refreshed for a maximum period of 90 days", I read this on many places while researching. Does user needs to login every 90 days?
  3. Can we get new refresh token without need of user to login again?
  4. How do we know if access token is expired or not? Do we need to call profile (or any other) API for to check expiration of access token?

Solution

    • If you are making using of MSAL library, then there is no need to pass any user information as during the initial authentication flow MSAL generates the refresh token and stores in its token cache.
    • MSAL library knows which user the refresh token belongs to based on the authentication context.
      try {
        const authResult = await cca.acquireTokenByCode(tokenRequest);
    
        const accessToken = authResult.accessToken;
    
        const refreshToken = () => {
          const tokenCache = cca.getTokenCache().serialize();
          const refreshTokenObject = JSON.parse(tokenCache).RefreshToken;
          const refreshToken =
            refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;
          return refreshToken;
        };
    
    • When you call cca.getTokenCache().serialize(), it returns the serialized token cache, which includes the refresh token for the user.

    Note that: Access token can only be refreshed for a maximum period of 90 days. Refer this MsDoc

    • Refresh tokens can be revoked and hence the application must handle rejections by the sign-in service gracefully by sending the user to an interactive sign-in prompt to sign in again.
    • The MSAL cache stores the refresh token so there is no need to sign in again.
    • MSAL will return a response with the relevant tokens if the access token is not expired and if the access token is expired and refresh token is valid then it will use the refresh token to retrieve a new access token and return a response.
    • Hence no need to need to call profile (or any other) API for to check expiration of access token. You can also check the expires_in property.

    enter image description here

    Reference:

    microsoft-authentication-library-for-js/lib/msal-browser/docs/token-lifetimes.md at dev · AzureAD/microsoft-authentication-library-for-js · GitHub