Deploy a single sign-on (SSO) Office Add-in to Microsoft Azure App Service

Office Add-ins that use SSO require server-side code. To support server-side code in deployment you need to use Azure App Service. Follow the steps in this article to deploy your Office Add-in to Azure App Service for staging or deployment.

Prerequisites

The steps in this article work for an Office Add-in created by the Yeoman Generator for Office Add-ins using the Office Add-in Task Pane project supporting single sign-on (localhost) project type. Be sure you have configured the add-in project so that it runs on localhost successfully. For more information, see the Single sign-on (SSO) quick start.

Note

For information about deploying an Office Add-in that you created using Teams Toolkit, see Deploy Teams app to the cloud and Deploy your first Teams app. Add-ins created with the Teams Toolkit use the unified manifest for Microsoft 365. For more information about publication of add-ins and sideloading, see Deploy and publish Office Add-ins.

The steps in this article also require:

Create the App Service app

The following steps set up a basic deployment of the Office Add-in. There are multiple ways to configure deployment that aren't covered in this documentation. For additional options on how to configure your deployment, see Deployment Best Practices.

Sign in to Azure

  1. Open your Office Add-in project in VS Code.

  2. Select the Azure logo in the Activity Bar. If the Activity Bar is hidden, open it by selecting View > Appearance > Activity Bar.

  3. In the App Service explorer, select Sign in to Azure... and follow the instructions.

    Sign in to Azure button selected in the Azure extension.

Configure the App Service app

App Service supports various versions of Node.js on both Linux and Windows. Select the tab for the one you'd like to use and then follow the instructions to create your App Service app.

  1. Right-click (or select and hold) on App Services and select Create new Web App. A Linux container is used by default.
  2. Type a globally unique name for your web app and press Enter. The name must be unique across all of Azure and use only alphanumeric characters ('A-Z', 'a-z', and '0-9') and hyphens ('-').
  3. In Select a runtime stack, select the Node 16 LTS runtime stack.
  4. In Select a pricing tier, select Free (F1) and wait for the resources to be provisioned in Azure. When prompted to deploy, don't deploy the add-in yet. You'll do that in a later step.
  1. Right-click (or select and hold) your App Service app and select Open in Portal.
  2. When the portal opens in the browser, copy the domain name of the URL (not the https:// part) from the Overview pane and save it. You'll need it in later steps.

Update package.json

  1. Open the package.json file. Then replace the start command in the "scripts" section with the following entry.

    "start": "node middletier.js",
    
  2. Find the "prestart" entry in the '"scripts"' section and delete it. This section is not needed for this deployment.

  3. Save the file.

Update webpack.config.js

  1. Open the webpack.config.js file.

  2. Set the urlDev and urlProd constants to the following values. (Note that the https protocol is not specified.) This will cause webpack to replace localhost:3000 with your web site domain name in the /dist/manifest.xml file.

    const urlDev = "localhost:3000";
    const urlProd = "<your-web-site-domain-name>";   
    
  3. Find the first CopyWebpackPlugin section and update it to also copy the package.json file to the dist folder as shown in the following example.

    new CopyWebpackPlugin({
        patterns: [
        {
            from: "assets/*",
            to: "assets/[name][ext][query]",
        },
        {
            from: "package.json",
            to: "package.json",
        },
        {
            from: "manifest*.xml",
            to: "[name]" + "[ext]",
            transform(content) {
                if (dev) {
                    return content;
                } else {
                    return content.toString().replace(new RegExp(urlDev, "g"), urlProd);
                }
            },
        },
      ],
    }),
    
  4. Save the file.

Update manifest

  1. Open the manifest.xml file.

  2. Replace <SupportUrl DefaultValue="https://www.contoso.com/help"/> with the URL of your web site help page.

  3. Replace <AppDomain>https://www.contoso.com</AppDomain> with the URL of your web site.

  4. In the <Scopes> section near the bottom of the file, add the openid scope as shown in the following XML.

    <Scopes>
        <Scope>User.Read</Scope>
        <Scope>profile</Scope>
        <Scope>openid</Scope>
    </Scopes>
    
  5. Save the file.

Update fallbackauthdialog.js (or fallbackauthdialog.ts)

  1. Open the src/helpers/fallbackauthdialog.js file, or src/helpers/fallbackauthdialog.ts if your project uses TypeScript.
  2. Find the redirectUri on line 24 and change the value to use your App Service app URL you saved previously. For example, redirectUri: "https://contoso-sso.azurewebsites.net/fallbackauthdialog.html",
  3. Save the file.

Update .ENV

The .ENV file contains a client secret. For the purposes of learning in this article you can deploy the .ENV file to Azure. However for a production deployment, you should move the secret and any other confidential data into Azure Key Vault.

  1. Open the .ENV file.
  2. Set the NODE_ENV variable to the value production.
  3. Save the file.

Update app.js (or app.ts)

The app.js (or app.ts) requires several minor changes to run correctly in a deployment. It's easiest to just replace the file with an updated version for deployment.

  1. Open the src/middle-tier/app.js file, or src/middle-tier/app.ts if your project uses TypeScript.

  2. Replace the entire file contents with the following code.

    /*
     * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See full license in root of repo. -->
     *
     * This file is the main Node.js server file that defines the express middleware.
     */
    
    require("dotenv").config();
    import * as createError from "http-errors";
    import * as path from "path";
    import * as cookieParser from "cookie-parser";
    import * as logger from "morgan";
    import express from "express";
    import { getUserData } from "./msgraph-helper";
    import { validateJwt } from "./ssoauth-helper";
    
    /* global console, process, require, __dirname */
    
    const app = express();
    const port = process.env.PORT;
    
    app.set("port", port);
    
    // view engine setup
    app.set("views", path.join(__dirname, "views"));
    app.set("view engine", "pug");
    
    app.use(logger("dev"));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    
    /* Turn off caching when developing */
    if (process.env.NODE_ENV !== "production") {
      app.use(express.static(path.join(process.cwd(), "dist"), { etag: false }));
    
      app.use(function (req, res, next) {
        res.header("Cache-Control", "private, no-cache, no-store, must-revalidate");
        res.header("Expires", "-1");
        res.header("Pragma", "no-cache");
        next();
      });
    } else {
      // In production mode, let static files be cached.
      app.use(express.static(path.join(process.cwd())));
      console.log("static set up: " + path.join(process.cwd()));
    }
    
    const indexRouter = express.Router();
    indexRouter.get("/", function (req, res) {
      res.sendFile("/taskpane.html", { root: __dirname });
    });
    
    // Route APIs
    indexRouter.get("/getuserdata", validateJwt, getUserData);
    
    app.use("/", indexRouter);
    
    // Catch 404 and forward to error handler
    app.use(function (req, res, next) {
      console.log("error 404");
      next(createError(404));
    });
    
    // error handler
    app.use(function (err, req, temp, res) {
      // set locals, only providing error in development
      console.log("error 500");
      res.locals.message = err.message;
      res.locals.error = req.app.get("env") === "development" ? err : {};
    
      // render the error page
      res.status(err.status || 500).send({
        message: err.message,
      });
    });
    
    app.listen(process.env.PORT, () => console.log("Server listening on port: " + process.env.PORT));    
    
  3. Save the file.

Update app registration

We recommend you create multiple app registrations for localhost, staging, and deployment testing. The following steps ensure that the app registration you use for deployment correctly uses the App Service app URL.

  1. In the Azure portal, open your app registration. Note that the app registration may be in a different account than your App Service app. Be sure to sign in to the correct account.

  2. In the left sidebar, select Authentication.

    The authentication page in the Azure app registration.

  3. On the Authentication pane, find the https://localhost:3000/fallbackauthdialog.html and change it to use the App Service app URL you saved previously. For example, https://contoso.sso.azurewebsites.net/fallbackauthdialog.html.

  4. Save the change.

  5. In the left sidebar, select Expose an API.

  6. Edit the Application ID URI field and replace localhost:3000 with the domain from the App Service app URL you saved previously.

  7. Save the changes.

Build and deploy

Once the files and app registration are updated, you can deploy the add-in.

  1. In VS Code open the terminal and run the command npm run build. This will build a folder named dist that you can deploy.
  2. In the VS Code Explorer browse to the dist folder. Right-click (or select and hold) the dist folder and select Deploy to Web App....
  3. When prompted to select a resource, select the App Service app you created previously.
  4. When prompted if you are sure, select Deploy.
  5. When prompted to always deploy the workspace, choose Yes.

If you make additional code changes, you'll need to run npm run build again and redeploy the project.

Deploy updates

You'll deploy updates to your web application in the same manner as described previously. Changes to the manifest require redistributing your manifest to users. The process to do so depends on your publishing method. For more information on updating your add-in, see Maintain your Office Add-in.

Test the deployment

Sideload the ./dist/manifest.xml file and test the functionality of the add-in in Office. For more information, see Sideload an Office Add-in for testing.

Note

If your app registration is on a different tenant than where you sideload the add-in, the add-in will not have admin consent for Microsoft Graph. To test in this scenario you need an admin to centrally deploy the add-in on the Microsoft 365 tenant. For more information, see Deploy add-ins in the Microsoft 365 admin center

If you encounter any deployment issues, see the Azure App Service troubleshooting documentation. If you use central deployment, or plan to deploy to AppSource, we recommend you validate the Office Add-in's manifest using the '-p' option for production.

Next steps