msal.js and Outlook web addin, issue when calling acquireTokenSilent
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:
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