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

Not getting refresh token 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 done:

  1. I have create app on azure. And I have added necessary permissions.

Permissions

  1. I am creating functions on a dummy project first. In that project I have successfully...

Here is the code

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

// In controller
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) => {  
  console.log('req.query: ', req.query);

  const tokenRequest = {
    scopes,
    code: req.query.code,
    redirectUri,
  };
  console.log('tokenRequest: ', tokenRequest);

  try {
    const authResult = await cca.acquireTokenByCode(tokenRequest);
    console.log('authResult: ', authResult);

    // 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');
  }
};

When I visit http://localhost:3000/api/login, I am being redirected to microsoft login page. After login I am getting on handleCallback

Issue is I am only getting access token, Refresh token is missing from response

{
  authority: 'https://login.microsoftonline.com/common/',
  uniqueId: '00000000-xxxx-ddd3b41006de',
  tenantId: '9188040d-xxxx-36a304b66dad',
  scopes: [ 'User.Read', 'Calendars.ReadWrite', 'openid', 'profile' ],
  account: {
    homeAccountId: '00000000-0000-xxxx.9188040d-6c67-4c5b-b112-36a304b66dad',
    environment: 'login.windows.net',
    tenantId: '9188040d-xxxx-36a304b66dad',
    username: '[email protected]',
    localAccountId: '00000000-xxxx-ddd3b41006de',
    name: 'Nikhil',
    nativeAccountId: undefined,
    authorityType: 'MSSTS',
    tenantProfiles: Map(1) { '9188040d-xxxx-36a304b66dad' => [Object] },
    idTokenClaims: {
      ver: '2.0',
      iss: 'https://login.microsoftonline.com/9188040d-xxxx-36a304b66dad/v2.0',
      sub: 'AAAAAAAAAAAAXXXXoWCiSKHIxQiLR5lA',
      aud: '945bf51b-xxxx-c5a83898b4b8',
      exp: 1710826775,
      iat: 1710740075,
      nbf: 1710740075,
      name: 'Nikhil Mandaniya',
      preferred_username: '[email protected]',
      oid: '00000000-xxxx-ddd3b41006de',
      tid: '9188040d-xxxx-36a304b66dad',
      aio: 'Dv2WQYyaZYlVxxxxsgCzJieAfDrhNDIE6Drp'
    },
    idToken: 'eyJ0eXAiOiJKV1QiLCJhxxxxoQ'
  },
  idToken: 'eyJ0eXAiOiJKV1QiLCJhxxxxoQ',
  idTokenClaims: {
    ver: '2.0',
    iss: 'https://login.microsoftonline.com/9188040d-xxxx-36a304b66dad/v2.0',
    sub: 'AAAAAAAAAAAAAAAAAAAAAICmPO0oWCiSKHIxQiLR5lA',
    aud: '945bf51b-xxxx-c5a83898b4b8',
    exp: 1710826775,
    iat: 1710740075,
    nbf: 1710740075,
    name: 'Nikhil Mandaniya',
    preferred_username: '[email protected]',
    oid: '00000000-xxxx-ddd3b41006de',
    tid: '9188040d-xxxx-36a304b66dad',
    aio: 'Dv2WQYyaZYlV19mDrMstNPvJY*xxxxDrhNDIE6Drp'
  },
  accessToken: 'EwBwA8l6BAAUTTy6dbu0OLf3Lzl3RxxxxzaNAg==',
  fromCache: false,
  expiresOn: 2024-03-18T06:39:35.000Z,
  extExpiresOn: 2024-03-18T07:39:35.000Z,
  refreshOn: undefined,
  correlationId: '7a3447ce-xxxx-761ea0a35fde',
  requestId: '29ad5633-xxxx-9f42812f1700',
  familyId: '',
  tokenType: 'Bearer',
  state: '',
  cloudGraphHostName: '',
  msGraphHost: '',
  code: undefined,
  fromNativeBroker: false
}

I am not sure What I am doing wrong. And What is idToken, Do I need to use that somewhere? As per my requirements, I am planing to get new access tokens using refresh tokens. I am open for suggetinon.


Solution

  • Suggestion from comments helped me get refresh token.

    Posting answer for people facing same problem.

    We have to get refresh tokens from token cache. This is how I have done it.

    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');
      }
    };