Skip to content

NAA WXP sample update #976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions Samples/auth/Office-Add-in-SSO-NAA/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,19 @@ For a list of supported hosts, see [NAA supported accounts and hosts](https://le
- Select **Register**.

1. On the **Office-Add-in-SSO-NAA** page, copy and save the value for the **Application (client) ID**. You'll use it in the next section.
1. Under **Manage** select **Authentication**.
1. In the **Single-page application** pane, select **Add URI**.
1. Enter the value `https://localhost:3000/auth.html` and select **Save**. This redirect handles the fallback scenario when browser auth is used from add-in.
1. In the **Single-page application** pane, select **Add URI**.
1. Enter the value `https://localhost:3000/dialog.html` and select **Save**. This redirect handles the fallback scenario when the Office dialog API is used.

For more information on how to register your application, see [Register an application with the Microsoft Identity Platform](https://learn.microsoft.com/graph/auth-register-app-v2).

### Configure the sample

1. Clone or download this repository.
1. From the command line, or a terminal window, go to the root folder of this sample at `/samples/auth/Office-Add-in-SSO-NAA`.
1. Open the `src/taskpane/authConfig.ts` file.
1. Open the `src/taskpane/msalconfig.ts` file.
1. Replace the placeholder "Enter_the_Application_Id_Here" with the Application ID that you copied.
1. Save the file.

Expand All @@ -74,9 +79,9 @@ For more information on how to register your application, see [Register an appli
`npm install`
`npm run start`

This will start the web server and sideload the add-in to Excel.
This will start the web server and sideload the add-in to Word.

1. In Excel, look for the **Show task pane** button and select it.
1. In Word, look for the **Show task pane** button and select it.
1. When the task pane opens, there are two buttons: **Get user data** and **Get user files**.
1. To see the signed in user's name and email, select **Get user data**.
1. To insert the first 10 filenames from the signed in user's Microsoft OneDrive, select **Get user files**.
Expand All @@ -85,9 +90,9 @@ You'll be prompted to consent to the scopes the sample needs when you select the

## Selecting hosts and debugging steps

If you want to choose Word or PowerPoint, modify the `start` command in the `package.json` file to match one of the following entries.
If you want to choose Excel or PowerPoint, modify the `start` command in the `package.json` file to match one of the following entries.

- For Word: `"start": "office-addin-debugging start manifest.xml desktop --app word",`
- For Word: `"start": "office-addin-debugging start manifest.xml desktop --app excel",`
- For PowerPoint: `"start": "office-addin-debugging start manifest.xml desktop --app powerpoint",`

You can also debug the sample by opening the project in VS Code.
Expand All @@ -105,8 +110,7 @@ For more information on debugging with VS Code, see [Debugging](https://code.vis
The `src/taskpane/authConfig.ts` file contains the MSAL code for configuring and using NAA. It contains a class named AccountManager which manages getting user account and token information.

- The `initialize` function is called from Office.onReady to configure and initialize MSAL to use NAA.
- The `ssoGetToken` function gets an access token for the signed in user to call Microsoft Graph APIs.
- The `ssoGetUserIdentity` function gets the account information of the signed in user. This can be used to get user details such as name and email.
- The `ssoGetAccessToken` function gets an access token for the signed in user to call Microsoft Graph APIs.

The `src/taskpane/document.ts` file contains code to write a list of file names, retrieved from Microsoft Graph, into the document. This works for Word, Excel, and PowerPoint documents.

Expand Down
49 changes: 38 additions & 11 deletions Samples/auth/Office-Add-in-SSO-NAA/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Samples/auth/Office-Add-in-SSO-NAA/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
"prettier": "office-addin-lint prettier",
"signin": "office-addin-dev-settings m365-account login",
"signout": "office-addin-dev-settings m365-account logout",
"start": "office-addin-debugging start manifest.xml desktop --app word",
"start": "office-addin-debugging start manifest.xml desktop --app excel",
"start:desktop": "office-addin-debugging start manifest.xml desktop",
"start:web": "office-addin-debugging start manifest.xml web",
"stop": "office-addin-debugging stop manifest.xml",
"validate": "office-addin-manifest validate manifest.xml",
"watch": "webpack --mode development --watch"
},
"dependencies": {
"@azure/msal-browser": "^3.24.0",
"@azure/msal-browser": "^4.12.0",
"core-js": "^3.37.1",
"regenerator-runtime": "^0.14.1"
},
Expand Down
168 changes: 133 additions & 35 deletions Samples/auth/Office-Add-in-SSO-NAA/src/taskpane/authConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,169 @@

/* This file provides MSAL auth configuration to get access token through nested app authentication. */

/* global console */
/* global Office, console, document*/

import { PublicClientNext, type IPublicClientApplication } from "@azure/msal-browser";
import {
BrowserAuthError,
createNestablePublicClientApplication,
type IPublicClientApplication,
} from "@azure/msal-browser";
import { msalConfig } from "./msalconfig";
import { createLocalUrl } from "./util";
import { getTokenRequest } from "./msalcommon";

export { AccountManager };

const applicationId = "Enter_the_Application_Id_Here";

const msalConfig = {
auth: {
clientId: applicationId,
authority: "https://login.microsoftonline.com/common",
supportsNestedAppAuth: true,
},
export type AuthDialogResult = {
accessToken?: string;
error?: string;
};

type DialogEventMessage = { message: string; origin: string | undefined };
type DialogEventError = { error: number };
type DialogEventArg = DialogEventMessage | DialogEventError;

// Encapsulate functions for getting user account and token information.
class AccountManager {
pca: IPublicClientApplication | undefined = undefined;
export class AccountManager {
private pca: IPublicClientApplication | undefined = undefined;
private _dialogApiResult: Promise<string> | null = null;
private _usingFallbackDialog = false;

private getSignOutButton() {
return document.getElementById("signOutButton");
}

private setSignOutButtonVisibility(isVisible: boolean) {
const signOutButton = this.getSignOutButton();
if (signOutButton) {
signOutButton.style.visibility = isVisible ? "visible" : "hidden";
}
}

private isNestedAppAuthSupported() {
return Office.context.requirements.isSetSupported("NestedAppAuth", "1.1");
}

// Initialize MSAL public client application.
async initialize() {
this.pca = await PublicClientNext.createPublicClientApplication(msalConfig);
// Make sure office.js is initialized
await Office.onReady();

// If auth is not working, enable debug logging to help diagnose.
this.pca = await createNestablePublicClientApplication(msalConfig);

// If Office does not support Nested App Auth provide a sign-out button since the user selects account
if (!this.isNestedAppAuthSupported() && this.pca.getActiveAccount()) {
this.setSignOutButtonVisibility(true);
}
this.getSignOutButton()?.addEventListener("click", () => this.signOut());
}

async ssoGetToken(scopes: string[]) {
const userAccount = await this.ssoGetUserIdentity(scopes);
return userAccount.accessToken;
private async signOut() {
if (this._usingFallbackDialog) {
await this.signOutWithDialogApi();
} else {
await this.pca?.logoutPopup();
}

this.setSignOutButtonVisibility(false);
}

/**
* Uses MSAL and nested app authentication to get the user account from Office SSO.
* This demonstrates how to work with user identity from the token.
*
* @returns The user account data (identity).
* @param scopes the minimum scopes needed.
* @returns An access token.
*/
async ssoGetUserIdentity(scopes: string[]) {
async ssoGetAccessToken(scopes: string[]) {
if (this._dialogApiResult) {
return this._dialogApiResult;
}

if (this.pca === undefined) {
throw new Error("AccountManager is not initialized!");
}

// Specify minimum scopes needed for the access token.
const tokenRequest = {
scopes: scopes
};

try {
console.log("Trying to acquire token silently...");
const userAccount = await this.pca.acquireTokenSilent(tokenRequest);
const authResult = await this.pca.acquireTokenSilent(getTokenRequest(scopes, false));
console.log("Acquired token silently.");
return userAccount;
return authResult.accessToken;
} catch (error) {
console.log(`Unable to acquire token silently: ${error}`);
console.warn(`Unable to acquire token silently: ${error}`);
}

// Acquire token silent failure. Send an interactive request via popup.
try {
console.log("Trying to acquire token interactively...");
const userAccount = await this.pca.acquireTokenPopup(tokenRequest);
const selectAccount = this.pca.getActiveAccount() ? false : true;
const authResult = await this.pca.acquireTokenPopup(getTokenRequest(scopes, selectAccount));
console.log("Acquired token interactively.");
return userAccount;
if (selectAccount) {
this.pca.setActiveAccount(authResult.account);
}
if (!this.isNestedAppAuthSupported()) {
this.setSignOutButtonVisibility(true);
}
return authResult.accessToken;
} catch (popupError) {
// Acquire token interactive failure.
console.log(`Unable to acquire token interactively: ${popupError}`);
throw new Error(`Unable to acquire access token: ${popupError}`);
// Optional fallback if about:blank popup should not be shown
if (popupError instanceof BrowserAuthError && popupError.errorCode === "popup_window_error") {
const accessToken = await this.getTokenWithDialogApi();
this.setSignOutButtonVisibility(true);
return accessToken;
} else {
// Acquire token interactive failure.
console.error(`Unable to acquire token interactively: ${popupError}`);
throw new Error(`Unable to acquire access token: ${popupError}`);
}
}
}

/**
* Gets an access token by using the Office dialog API to handle authentication. Used for fallback scenario.
* @returns The access token.
*/
async getTokenWithDialogApi(): Promise<string> {
this._dialogApiResult = new Promise((resolve, reject) => {
Office.context.ui.displayDialogAsync(createLocalUrl(`dialog.html`), { height: 60, width: 30 }, (result) => {
result.value.addEventHandler(Office.EventType.DialogEventReceived, (arg: DialogEventArg) => {
const errorArg = arg as DialogEventError;
if (errorArg.error == 12006) {
this._dialogApiResult = null;
reject("Dialog closed");
}
});
result.value.addEventHandler(Office.EventType.DialogMessageReceived, (arg: DialogEventArg) => {
const messageArg = arg as DialogEventMessage;
const parsedMessage = JSON.parse(messageArg.message);
result.value.close();

if (parsedMessage.error) {
reject(parsedMessage.error);
this._dialogApiResult = null;
} else {
resolve(parsedMessage.accessToken);
this.setSignOutButtonVisibility(true);
this._usingFallbackDialog = true;
}
});
});
});
return this._dialogApiResult;
}

signOutWithDialogApi(): Promise<void> {
return new Promise((resolve) => {
Office.context.ui.displayDialogAsync(
createLocalUrl(`dialog.html?logout=1`),
{ height: 60, width: 30 },
(result) => {
result.value.addEventHandler(Office.EventType.DialogMessageReceived, () => {
this.setSignOutButtonVisibility(false);
this._dialogApiResult = null;
resolve();
result.value.close();
});
}
);
});
}
}
13 changes: 13 additions & 0 deletions Samples/auth/Office-Add-in-SSO-NAA/src/taskpane/fallback/auth.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. -->
<!-- This file shows how to design a first-run page that provides a welcome screen to the user about the features of the add-in. -->

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Auth Page</title>
</head>
<body></body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. -->
<!-- This file shows how to design a first-run page that provides a welcome screen to the user about the features of the add-in. -->

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Dialog</title>

<!-- Office JavaScript API -->
<script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
</head>

<body></body>
</html>
Loading