6

I hope you can help me: I currently develop an app which needs access to the users calendar (outlook-calendar) to find free meeting slots (other users will be able to see and then select one of the free slots - similar to calendly). For that I use msal-node to authenticate against azureAD. But my use case needs "everytime"-access to the calendars from all users. This is why I want to get an refresh_token. The docs of msal-node say that I should provide the offline_scope to get an refreshtoken while doing the OAuth-process. My problem is that I receive an access_token and id_token and so on, but no refreshtoken. The Azure-response further shows a successful answer but when I take a look into the returned scopes I cannot find offline_scope. You can see the returned scopes here

What should I do?

I use a cofidentalClientApplication msal-node instance:

  const oauth2Client = MicrosoftClient.Connection
  const authCodeUrlParameters = {
    scopes: ["offline_access", "user.read"],
    forceRefresh: true,
    redirectUri: "http://localhost:3000/outlookRedirect",
  }
  try {
    console.log("GDFHGJF")
    return oauth2Client.getAuthCodeUrl(authCodeUrlParameters)
  }

After receiving the code from Azure, I process it via:

  const oauth2Client = MicrosoftClient.Connection
  const tokenRequest = {
    code: code,
    scopes: ["user.read", "offline_access"],
    forceRefresh: true,
    redirectUri: "http://localhost:3000/outlookRedirect",
    //client_secret: process.env.MICROSOFTCLIENTSECRET,
  }
  const testus = await oauth2Client.acquireTokenByCode(tokenRequest)
  const tokenRequest2 = {
    scopes: ["user.read", "offline_access"],
    forceRefresh: true,
    redirectUri: "http://localhost:3000/outlookRedirect",
    account: testus.account,
  }

  oauth2Client
    .acquireTokenSilent(tokenRequest2)
    .then((response) => {
      console.log("\nResponse: \n:", response)
    })
    .catch((error) => {
      console.log(error)
    })
  return

What is my fault? I appreciate any kind of help!

Thank you in advance, Lukas

Lasklu
  • 103
  • 1
  • 5

3 Answers3

8

after calling 'acquireTokenByCode' , 'pca' now has the refresh token. const tokenCache = pca.getTokenCache().serialize(); const refreshTokenObject = (JSON.parse(tokenCache)).RefreshToken const refreshToken = refreshTokenObject[Object.keys(refreshTokenObject)[0]].secret;

Below is a complete snippet of How to get the Refresh and Access token.

/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
const express = require("express");
const msal = require('@azure/msal-node');

const SERVER_PORT = process.env.PORT || 3000;
const REDIRECT_URI = "http://localhost:3000/redirect";

// Before running the sample, you will need to replace the values in the config, 
// including the clientSecret
const config = {
    auth: {
        clientId: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
        authority: "https://login.microsoftonline.com/84fb56d3-e15d-4ae1-acd7-cbf83c4c0af3",
        clientSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: msal.LogLevel.Verbose,
        }
    }
};

// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);

// Create Express App and Routes
const app = express();

app.get('/', (req, res) => {
    const authCodeUrlParameters = {
        scopes: ["user.read","offline_access"],
        redirectUri: REDIRECT_URI,
        prompt:'consent'
    };
    // get url to sign user in and consent to scopes needed for application
    pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
        res.redirect(response);
    }).catch((error) => console.log(JSON.stringify(error)));
});

app.get('/redirect', (req, res) => {
    const tokenRequest = {
        code: req.query.code,
        scopes: ["user.read","offline_access"],
        redirectUri: REDIRECT_URI,
        accessType: 'offline',
    };
    pca.acquireTokenByCode(tokenRequest).then((response) => {
        const accessToken = response.accessToken;
        const refreshToken = () => {
            const tokenCache = pca.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)
        res.sendStatus(200);
    }).catch((error) => {
        console.log(error);
        res.status(500).send(error);
    });
});


app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
THEODORE
  • 917
  • 10
  • 12
6

msal-node does not expose the refresh token to the end user by design. It is stored and used internally under the hood when you need a new access token. You should call acquireTokenSilent each time you need an access token and msal-node will manage the tokens by either returning a cached token to you or using the refresh token to acquire a new access token.

For more context: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/2836

Thomas Norling
  • 444
  • 2
  • 5
0

In addition to the accepted answer, its important to note that the MSAL cache can have many authenticated users (and lots of refresh tokens). Here is my solution to extract the exact refresh token for a specific user.

I use this on each login to exact the individuals refresh token and store it.

public extractRefresh = ( homeAccountId : string ) : string =>
{
    try
    {
        const tokenCache         = this.msalClientApp.getTokenCache().serialize();
        const refreshTokenObject = ( JSON.parse( tokenCache ) ).RefreshToken;
            
        let refreshToken = '';

        Object.entries( refreshTokenObject ).forEach( ( item : any )  => 
        {
            if ( item[1].home_account_id === homeAccountId )
            {
                refreshToken = item[1].secret;
            }
        });

        return refreshToken;
    }
    catch
    {
        return '';
    }
}
Vidarious
  • 746
  • 2
  • 10
  • 22