msal.js and Outlook web addin, issue when calling acquireTokenSilent

Pablo Glomby 186 Reputation points
2024-01-18T17:48:51.9333333+00:00

Hello. I am writing an Outlook web addin. It uses msal-browser (the latest version). There are basically two pages: taskpane.html that is the "main" page and login.html that is the login page. This is the code for taskpane.js:

import * as Msal from '@azure/msal-browser';  
let outlookUserName = "";
let access_token = "";  
const msalConfig = {
     auth: {
         clientId: "<client id>",
         authority: "https://login.microsoftonline.com/<tenant>",
         redirectUri: "https://localhost:3000/taskpane.html",
     },
     cache: {
         cacheLocation: "localStorage", // Needed to avoid "User login is required" error.
         storeAuthStateInCookie: true, // Recommended to avoid certain IE/Edge issues.
     },
     system: {
         loggerOptions: {
             loggerCallback: (level, message, containsPii) => {
                 if (containsPii) {
                     return;
                 }
                 console.warn(message);
                 return;
             }
         }
     }
 };
  const loginRequest = {
     scopes: ["User.Read"] 
};  
  const myMSALObj = new Msal.PublicClientApplication(msalConfig);
  await myMSALObj.initialize();
 myMSALObj.handleRedirectPromise()
     .then(handleResponse)
     .catch((error) => {
         console.error(error);
     });

  function handleResponse(response) {
     if (response !== null) {
         console.log("--->response.account.username=" + response.account.username);
     } else {
         console.log("--->In handleResponse but response is null");
         const currentAccounts = myMSALObj.getAllAccounts();
         if (currentAccounts === null) {
             console.log("--->In handleResponse currentAccounts === null");
             return;
         } else if (currentAccounts.length > 1) {
             // Add choose account code here
             console.log("MsalAuthService >>> handleResponse >>> Multiple accounts detected.");
         } else if (currentAccounts.length === 0) {
             console.log("--->In handleResponse currentAccounts.length=0");
             return;
         } else if (currentAccounts.length === 1) {
             console.log("--->In handleResponse currentAccounts.length=1");
         }
     }
 }

  Office.onReady(function (info) {
     if (info.host === Office.HostType.Outlook) {
         document.getElementById("sideload-msg").style.display = "none";
         document.getElementById("app-body").style.display = "flex";
         document.getElementById("run").onclick = run;
     }
 });

  export async function run() {
      outlookUserName = getOLUserName();
     var tokenGot = "";
      if (outlookUserName) {
         loginRequest.account = myMSALObj.getAccountByUsername(outlookUserName);
         await myMSALObj.acquireTokenSilent(loginRequest).then(async (response) => {
             if (response) {
                 tokenGot = JSON.parse(response).result.accessToken;
             } else {
                 myMSALObj.loginRedirect(loginRequest);
             }
         }).catch(error => {
             console.log("----->Error getting silently: " + error);
         });
          access_token = tokenGot;
     }
      if (tokenGot === "") {
          let dialogLoginUrl = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '') + '/login.html?userName=' + outlookUserName;
         let loginDialog;
          await Office.context.ui.displayDialogAsync(dialogLoginUrl, { height: 40, width: 30 },
             function (asyncResult) {
                 const dialog = asyncResult.value;
                 dialog.addEventHandler(Office.EventType.DialogMessageReceived, (arg) => {
                     dialog.close();
                     processLoginMessage(arg);
                     console.log("----->ACCESS TOKEN: " + access_token);
                 });
             }
         );
     }
 }

  function processLoginMessage(arg) {
     let messageFromDialog = JSON.parse(arg.message);
     if (messageFromDialog.status === 'success') {
         access_token = messageFromDialog.result.accessToken
         document.getElementById("pleaseLogInLabel").innerHTML = "<b>User has successfully logged in</b>";
         document.getElementById("run").style.display = "none";
     }
 }

  function getOLUserName() {
     try {
         return Office.context.mailbox.userProfile.emailAddress;
     } catch (exception) {
        return "";
     }
 }  

As you can see, what I do is to first try to login silently. The problem is that myMSALObj.getAccountByUsername returns null. When handleResponse is called, I get: loginRequest.account: null So all this tells me that there is a problem with the cache. As a result of all this, of course acquireTokenSilent fails with: User's image

I am well logged in because in login.js, getAccountByUsername returns the good object and acquireTokenSilent is just fine. login.js is:

//import { PublicClientApplication } from "@azure/msal-browser"; 
import * as Msal from '@azure/msal-browser';  

let username = "";  

const msalConfig = {
     auth: {
         clientId: "<app id>",
         authority: "https://login.microsoftonline.com/<tenant>",
     },
     cache: {
         cacheLocation: "localStorage", // Needed to avoid "User login is required" error.
         storeAuthStateInCookie: true, // Recommended to avoid certain IE/Edge issues.
     },
     system: {
         loggerOptions: {
             loggerCallback: (level, message, containsPii) => {
                 if (containsPii) {
                     return;
                 }
                 console.warn(message);
                 return;
             }
         }
     }
 };

 const loginRequest = {
     scopes: ["User.Read"]
 };
  const myMSALObj = new Msal.PublicClientApplication(msalConfig);

   (() => {
     Office.initialize = async () => {
         const urlParams = new URLSearchParams(window.location.search);
         username = urlParams.get('userName')
         document.getElementById("LogInLabel").innerHTML = "Logging is as " + username;
         await myMSALObj.initialize();
         loginRequest.account = myMSALObj.getAccountByUsername(username);
          myMSALObj.acquireTokenSilent(loginRequest).then(async (response) => {
             if (response) {
                 Office.context.ui.messageParent(JSON.stringify({ status: 'success', result: response }));
             } else {
                 myMSALObj.loginRedirect(loginRequest);
             }
         }).catch(error => {
             console.warn("silent token acquisition fails. acquiring token using popup");
             // fallback to interaction when silent call fails
             myMSALObj.handleRedirectPromise()
                 .then(async (response) => {
                     if (response) {
                         Office.context.ui.messageParent(JSON.stringify({ status: 'success', result: response }));
                     } else {
                         myMSALObj.loginRedirect(loginRequest);
                     }
                 })
                 .catch((errorDesc) => {
                     document.getElementById("LogInLabel").innerHTML = ("Error in interactive login:" + errorDesc);
                 });
         });
      };
 })(); 

In login.js all works just fine... the problem is in taskpane.js It smells like if each page is "sandboxed"... the Office Dialog API adds a lot of coding to each .js, maybe it's this? Any idea? Thanks

Outlook
Outlook
A family of Microsoft email and calendar products.
3,427 questions
JavaScript API
JavaScript API
An Office service that supports add-ins to interact with objects in Office client applications.
942 questions
Office Development
Office Development
Office: A suite of Microsoft productivity software that supports common business tasks, including word processing, email, presentations, and data management and analysis.Development: The process of researching, productizing, and refining new or existing technologies.
3,720 questions
0 comments No comments
{count} votes