with TypeScript SSO

Open File Explorer and create the (C:\temp\vscode-yeoman) project folder.
Open Visual Studio Code.
Display the Terminal window (View > Terminal).
Change to that folder.

cd C:\temp\vscode-yeoman 

Run the Yeoman generator to create the project.

yo office 

Choose "Office Add-in Task Pane project supporting single sign-on".
Use the arrow keys and press Enter to select.

alt text

Choose a script type: Select "TypeScript".
What do you want to name your add-in: Type "Excel-TypeScript-SSO".
Which office client application would you like to support: Select "Excel".
All the necessary files will be created for you.


package.json file

Change to that folder.

cd Excel-TypeScript-SSO 

Open the Explorer pane (View > Explorer).
Open this folder in the current VS Code instance.

code -a . 

Click on the package.json file.

alt text
{ 
  "name": "office-addin-taskpane-sso",
  "version": "0.0.0",
  "private": true,
"config": {
"app_to_debug": "excel",
"app_type_to_debug": "desktop",
"dev_server_port": 3000
},
  "scripts": {
    "build": "webpack --mode production",
    "build:dev": "webpack --mode development",
    "configure-sso": "office-addin-sso configure manifest.xml",
    "dev-server": "webpack serve --mode development",
    "lint": "office-addin-lint check",
    "lint:fix": "office-addin-lint fix",
    "prettier": "office-addin-lint prettier",
    "sideload": "office-addin-debugging start manifest.xml",
    "start": "npm run build:dev && concurrently \"npm run start:server\" \"npm run sideload\"",
    "start:server": "office-addin-sso start manifest.xml",
    "stop": "office-addin-debugging stop manifest.xml",
    "validate": "office-addin-manifest validate manifest.xml",
    "watch": "webpack --watch --mode development"
  },
  "dependencies": {
    "core-js": "^3.9.1",
    "dotenv": "^8.2.0",
    "msal": "^1.3.2",
    "node-fetch": "^2.6.1",
    "office-addin-sso": "^1.2.8",
    "regenerator-runtime": "^0.13.7"
  },
  "devDependencies": {
    "@babel/core": "^7.13.10",
    "@babel/preset-typescript": "^7.13.0",
    "@types/jquery": "^3.3.31",
    "@types/office-js": "^1.0.180",
    "@types/office-runtime": "^1.0.17",
    "acorn": "^8.5.0",
    "babel-loader": "^8.2.2",
    "buffer": "^6.0.3",
    "concurrently": "^6.3.0",
    "copy-webpack-plugin": "^9.0.1",
    "eslint": "^7.32.0",
    "eslint-plugin-office-addins": "^2.0.0",
    "file-loader": "^6.2.0",
    "html-loader": "^2.1.2",
    "html-webpack-plugin": "^5.3.2",
    "https-browserify": "^1.0.0",
    "office-addin-cli": "^1.3.5",
    "office-addin-debugging": "^4.3.8",
    "office-addin-dev-certs": "^1.7.7",
    "office-addin-lint": "^2.0.0",
    "office-addin-manifest": "^1.7.7",
    "office-addin-prettier-config": "^1.1.4",
    "source-map-loader": "^3.0.0",
    "stream-http": "^3.2.0",
    "ts-loader": "^9.2.5",
    "typescript": "^4.3.5",
    "url": "0.11.0",
    "webpack": "^5.50.0",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "4.7.3"
  },
  "prettier": "office-addin-prettier-config",
  "browserslist": [
    "ie 11"
  ]
}

manifest.xml

Click on the manifest.xml file.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0"
           xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides"
           xsi:type="TaskPaneApp">
  <Id>87dde121-886c-418e-b4e1-45afe8f5fccc</Id>
  <Version>1.0.0.0</Version>
  <ProviderName>Better Solutions Limited</ProviderName>
  <DefaultLocale>en-US</DefaultLocale>
  <DisplayName DefaultValue="Excel-TypeScript-SSO"/>
  <Description DefaultValue="An add-in that shows how to use SSO."/>
  <IconUrl DefaultValue="https://localhost:{PORT}/assets/icon-32.png"/>
  <HighResolutionIconUrl DefaultValue="https://localhost:{PORT}/assets/icon-64.png"/>
  <SupportUrl DefaultValue="https://bettersolutions.com"/>
  <AppDomains>
    <AppDomain>AppDomain1</AppDomain>
  </AppDomains>
  <Hosts>
    <Host Name="Workbook"/>
  </Hosts>
  <DefaultSettings>
    <SourceLocation DefaultValue="https://localhost:{PORT}/taskpane.html"/>
  </DefaultSettings>
  <Permissions>ReadWriteDocument</Permissions>
  <VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0">
    <Hosts>
      <Host xsi:type="Workbook">
        <DesktopFormFactor>
          <GetStarted>
            <Title resid="GetStarted.Title"/>
            <Description resid="GetStarted.Description"/>
            <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/>
          </GetStarted>
          <ExtensionPoint xsi:type="PrimaryCommandSurface">
            <OfficeTab id="TabHome">
              <Group id="CommandsGroup">
                <Label resid="CommandsGroup.Label"/>
                <Icon>
                  <bt:Image size="16" resid="Icon.16x16"/>
                  <bt:Image size="32" resid="Icon.32x32"/>
                  <bt:Image size="80" resid="Icon.80x80"/>
                </Icon>
                <Control xsi:type="Button" id="TaskpaneButton">
                  <Label resid="TaskpaneButton.Label"/>
                  <Supertip>
                    <Title resid="TaskpaneButton.Label"/>
                    <Description resid="TaskpaneButton.Tooltip"/>
                  </Supertip>
                  <Icon>
                    <bt:Image size="16" resid="Icon.16x16"/>
                    <bt:Image size="32" resid="Icon.32x32"/>
                    <bt:Image size="80" resid="Icon.80x80"/>
                  </Icon>
                  <Action xsi:type="ShowTaskpane">
                    <TaskpaneId>ButtonId1</TaskpaneId>
                    <SourceLocation resid="Taskpane.Url"/>
                  </Action>
                </Control>
              </Group>
            </OfficeTab>
          </ExtensionPoint>
        </DesktopFormFactor>
      </Host>
    </Hosts>
    <Resources>
      <bt:Images>
        <bt:Image id="Icon.16x16" DefaultValue="https://localhost:{PORT}/assets/icon-16.png"/>
        <bt:Image id="Icon.32x32" DefaultValue="https://localhost:{PORT}/assets/icon-32.png"/>
        <bt:Image id="Icon.80x80" DefaultValue="https://localhost:{PORT}/assets/icon-80.png"/>
      </bt:Images>
      <bt:Urls>
        <bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://go.microsoft.com/fwlink/?LinkId=276812"/>
        <bt:Url id="Commands.Url" DefaultValue="https://localhost:{PORT}/commands.html"/>
        <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:{PORT}/taskpane.html"/>
      </bt:Urls>
      <bt:ShortStrings>
        <bt:String id="GetStarted.Title" DefaultValue="Get started with your sample add-in!"/>
        <bt:String id="CommandsGroup.Label" DefaultValue="Commands Group"/>
        <bt:String id="TaskpaneButton.Label" DefaultValue="Show Taskpane"/>
      </bt:ShortStrings>
      <bt:LongStrings>
        <bt:String id="GetStarted.Description" DefaultValue="Your sample add-in loaded succesfully."/>
        <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Click to Show a Taskpane"/>
      </bt:LongStrings>
    </Resources>
    <WebApplicationInfo>
      <Id>{application GUID here}</Id>
      <Resource>api://localhost:{PORT}/{application GUID here}</Resource>
      <Scopes>
        <Scope>User.Read</Scope>
        <Scope>profile</Scope>
      </Scopes>
    </WebApplicationInfo>
  </VersionOverrides>
</OfficeApp>

webpack.config.js

Click on the webpack.config.js file.

/* eslint-disable no-undef */ 

const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const urlDev = "https://localhost:3000/";
const urlProd = "https://bettersolutions.com/";

module.exports = async (env, options) => {
  const dev = options.mode === "development";
  const buildType = dev ? "dev" : "prod";
  const config = {
    devtool: "source-map",
    entry: {
      polyfill: ["core-js/stable", "regenerator-runtime/runtime"],
      taskpane: "./src/taskpane/taskpane.ts",
      commands: "./src/commands/commands.ts",
      fallbackauthdialog: "./src/helpers/fallbackauthdialog.ts",
    },
    output: {
      devtoolModuleFilenameTemplate: "webpack:///[resource-path]?[loaders]",
      clean: true,
    },
    resolve: {
      extensions: [".ts", ".tsx", ".html", ".js"],
      fallback: {
        buffer: require.resolve("buffer/"),
        http: require.resolve("stream-http"),
        https: require.resolve("https-browserify"),
        url: require.resolve("url/"),
      },
    },
    module: {
      rules: [
        {
          test: /\.ts$/,
          exclude: /node_modules/,
          use: {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-typescript"],
            },
          },
        },
        {
          test: /\.tsx?$/,
          exclude: /node_modules/,
          use: "ts-loader",
        },
        {
          test: /\.html$/,
          exclude: /node_modules/,
          use: "html-loader",
        },
        {
          test: /\.(png|jpg|jpeg|gif|ico)$/,
          type: "asset/resource",
          generator: {
            filename: "assets/[name][ext][query]",
          },
        },
      ],
    },
    plugins: [
      new HtmlWebpackPlugin({
        filename: "taskpane.html",
        template: "./src/taskpane/taskpane.html",
        chunks: ["polyfill", "taskpane"],
      }),
      new HtmlWebpackPlugin({
        filename: "commands.html",
        template: "./src/commands/commands.html",
        chunks: ["polyfill", "commands"],
      }),
      new HtmlWebpackPlugin({
        filename: "fallbackauthdialog.html",
        template: "./src/helpers/fallbackauthdialog.html",
        chunks: ["polyfill", "fallbackauthdialog"],
      }),
      new CopyWebpackPlugin({
        patterns: [
          {
            from: "assets/*",
            to: "assets/[name][ext][query]",
          },
          {
            from: "manifest*.xml",
            to: "[name]." + buildType + "[ext]",
            transform(content) {
              if (dev) {
                return content;
              } else {
                return content.toString().replace(new RegExp(urlDev, "g"), urlProd);
              }
            },
          },
        ],
      }),
    ],
  };
  return config;
};

Other Source Files

The other project files are displayed on this page for reference.


PORT Number

Before you do anything else, you need to decide or find out which port number you will be testing on.
We will be using 8080, but yours might be different.


SSO Manual App Registration

To work with SSO you need to register your Office Add-in with the Microsoft identity platform.
The following command can be run to set this up for you.
Before you run this though, it is worth noting that it will install Microsoft Azure CLI which is 250 MB.
Also worth noting is that this command is not always successful.

npm configure-sso 

For these reasons we have outlined the manual steps here.

alt text

Edit package.json

Change the port number.

{ 
"name": "office-addin-taskpane-sso",
"version": "0.0.0",
  "private": true,
"config": {
"app_to_debug": "excel",
"app_type_to_debug": "desktop",
"dev_server_port": 8080

Edit manifest.xml

Insert the "Application (client) ID" into the WebApplicationInfo tag.
Add the Client ID to the ID tag.
Add the Port Number and Client ID to the Resource tag.

    <WebApplicationInfo> 
      <Id> Application Client ID </Id>
      <Resource> api://localhost:8080/Application Client ID </Resource>
      <Scopes>
        <Scope>User.Read</Scope>
        <Scope>profile</Scope>
      </Scopes>
    </WebApplicationInfo>
  </VersionOverrides>
</OfficeApp>

Edit webpack.config.js

Change the Port Number in the urlDev.

const urlDev = "https://localhost:8080/"; 
const urlProd = "https://bettersolutions.com/";

Edit .ENV

Insert the Client ID to the CLIENT_ID constant.
Insert the Port Number to the PORT constant.

CLIENT_ID= Application Client ID 
GRAPH_URL_SEGMENT=/me
NODE_ENV=development
PORT= 8080
QUERY_PARAM_SEGMENT=
SCOPE=User.Read

Edit fallbackauthdialog.ts

Click on the src/helpers/fallbackauthdialog.ts file.
Add the Client ID to the clientId parameter.
Add the Port Number to the redirectUrl parameter.

const msalConfig: Msal.Configuration = { 
  auth: {
    clientId: "Application Client ID",
    authority: "https://login.microsoftonline.com/common",
    redirectUri: "https://localhost:8080/fallbackauthdialog.html",
    navigateToLoginRequestUrl: false,
  },
  cache: {
    cacheLocation: "localStorage",
    storeAuthStateInCookie: true,
  },
};

Build the Add-in

Execute the following command.

npm run build:dev 

Check that the project builds successfully.


Start the Local Server

Execute the following command.

npm run start:server 

You should see a Security Warning message asking if you want to install the certificate.
Press Yes.
Check the server is running on 8080.
Press Ctrl + C to stop the server and press Y, Enter to terminate the batch job.


Load the Add-in - Desktop

Execute the following command.

npm run start 
alt text

Customise the Add-in Further

link - learn.microsoft.com/en-us/office/dev/add-ins/quickstarts/sso-quickstart 
link - learn.microsoft.com/en-us/office/dev/add-ins/develop/sso-in-office-add-ins
link - learn.microsoft.com/en-us/office/dev/add-ins/develop/create-sso-office-add-ins-nodejs

© 2022 Better Solutions Limited. All Rights Reserved. © 2022 Better Solutions Limited TopPrevNext