Schreiben einer Node.js-App zum Abrufen von Outlook-Mail, -Kalender und -KontaktenWrite a Node.js app to get Outlook mail, calendar, and contacts

In diesem Leitfaden werden Sie schrittweise durch den Prozess des Erstellens einer einfachen Node.js-App zum Abrufen von Nachrichten in Office 365 oder Outlook.com geführt. Wenn Sie die hier beschriebenen Schritte ausführen, sollte der Quellcode in diesem Repository das Ergebnis sein.The purpose of this guide is to walk through the process of creating a simple Node.js app that retrieves messages in Office 365 or Outlook.com. The source code in this repository is what you should end up with if you follow the steps outlined here.

In diesem Leitfaden wird Microsoft Graph zum Zugriff auf Outlook-Mail verwendet. Microsoft empfiehlt die Verwendung von Microsoft Graph für den Zugriff auf Outlook-Mail, -Kalender und -Kontakte. Verwenden Sie die Outlook-APIs nur dann direkt (über https://outlook.office.com/api), wenn Sie ein Feature benötigen, das in den Graph-Endpunkten nicht verfügbar ist. Eine Version dieses Beispiels mit Verwendung der Outlook-APIs finden Sie in dieser Verzweigung.This guide will use Microsoft Graph to access Outlook mail. Microsoft recommends using Microsoft Graph to access Outlook mail, calendar, and contacts. You should use the Outlook APIs directly (via https://outlook.office.com/api) only if you require a feature that is not available on the Graph endpoints. For a version of this sample that uses the Outlook APIs, see this branch.

In diesem Leitfaden wird davon ausgegangen, dass Sie Node.js bereits installiert haben und auf Ihrem Entwicklungscomputer ausführen.This guide assumes that you already have Node.js installed and working on your development machine.

Erstellen der AppCreate the app

Lassen Sie uns direkt loslegen.Let's dive right in! Im ersten Schritt installieren wir den Express-Generator.The first step is to install the Express generator. Damit wird das gesamte Gerüst Ihrer App verarbeitet.We'll use this to handle all the scaffolding for our app. Öffnen Sie in der Befehlszeile oder Verwaltungsshell ein Verzeichnis, in dem Sie Ihre App erstellen möchten, und führen Sie den folgenden Befehl aus:Open your command prompt or shell to a directory where you'd like to create your app and run the following command:

npm install -g express-generator

Führen Sie nun den folgenden Befehl aus, um eine Express-App zu erstellen, die Handlebars als Rendermodul verwendet:Now run the following command to create an Express app that uses Handlebars as the rendering engine:

express --hbs node-tutorial

Dadurch werden ein neues untergeordnetes Verzeichnis namens node-tutorial und die Dateistruktur für die App erstellt.This will create a new sub-directory called node-tutorial and create the file structure for the app. Ändern Sie das aktuelle Verzeichnis für Ihre Verwaltungsshell in das Verzeichnis node-tutorial, und führen Sie den folgenden Befehl aus, um Abhängigkeiten zu installieren.Change the current directory for your shell to the node-tutorial directory and run the following command to install dependencies.

npm install

An diesem Punkt sollten Sie über eine funktionierende App verfügen.At this point, you should have a working app. Führen Sie den folgenden Befehl im Verzeichnis node-tutorial aus.Run the following command in the node-tutorial directory.

npm start

Öffnen Sie Ihren Browser und navigieren Sie zu http://localhost:3000.Open your browser and navigate to http://localhost:3000. Die Meldung "Willkommen bei Express" wird angezeigt.You should see a "Welcome to Express" message.

Da wir nun bestätigt haben, dass die App funktioniert, können wir weiter damit arbeiten.Now that we've confirmed that the app is working, we're ready to start making it do something.

Entwerfen der AppDesigning the app

Unsere App ist sehr einfach. Wenn ein Benutzer die Website besucht, sieht er eine Schaltfläche zum Anmelden und Anzeigen seiner E-Mails. Beim Klicken auf die Schaltfläche gelangt er zur Azure-Anmeldeseite, wo er sich mit seinem Office 365- oder Outlook.com-Konto anmelden kann und Zugriff auf unsere App erhält. Schließlich wird er zurück zu unserer App geleitet, die eine Liste der neuen E-Mails im Posteingang des Benutzers anzeigt.Our app will be very simple. When a user visits the site, they will see a button to log in and view their email. Clicking that button will take them to the Azure login page where they can login with their Office 365 or Outlook.com account and grant access to our app. Finally, they will be redirected back to our app, which will display a list of the most recent email in the user's inbox.

Zunächst werden wir mit Bootstrap einige grundlegende Formatierungen hinzufügen.Let's begin by adding some basic styling to the app with Bootstrap. Öffnen Sie die Datei ./views/layout.hbs, und ersetzen Sie den gesamten Inhalt durch Folgendes:Open the ./views/layout.hbs file and replace the entire contents with the following.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>{{title}}</title>
    <!-- Bootstrap core CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <!-- Nav bar -->
    <nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
      <a class="navbar-brand" href="/">Node Graph Tutorial</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarCollapse">
        <ul class="navbar-nav mr-auto">
          <li class="nav-item{{#if active.home}} active{{/if}}">
            <a class="nav-link" href="/">Home</a>
          </li>
          {{#if user}}
            <li class="nav-item{{#if active.inbox}} active{{/if}}">
              <a class="nav-link" href="/mail">Inbox</a>
            </li>
          {{/if}}
        </ul>
        <ul class="navbar-nav justify-content-end">
          {{#if user}}
            <li class="nav-item dropdown">
              <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{{user}}</a>
              <div class="dropdown-menu">
                  <a class="dropdown-item" href="/authorize/signout">Sign Out</a>
              </div>
            </li>
          {{else}}
            <li class="nav-item">
              <a class="nav-link" href="{{signInUrl}}">Sign In</a>
            </li>
          {{/if}}
        </ul>
      </div>
    </nav>

    <!-- Main body -->
    <main role="main" class="container">
      {{{body}}}

      <!-- Debug output -->
      {{#if debug}}
        <div class="card">
          <div class="card-body">
            <h5 class="card-title">Debug</h5>
            <pre class="bg-light border p-3"><code>{{debug}}</code></pre>
          </div>
        </div>
      {{/if}}
    </main>

    <!-- Bootstrap core JavaScript
    ================================================== -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
  </body>
</html>

Dadurch werden Bootstrap und die Abhängigkeiten in das Layout für jede Seite der App eingefügt, und eine einfache Navigationsleiste wird erstellt.This adds Bootstrap and its dependencies into the layout for every page in the app, and creates a basic nav bar. Jetzt aktualisieren wir die Startseite.Now let's update the home page. Öffnen Sie die Datei ./views/index.hbs, und ersetzen Sie ihren Inhalt durch Folgendes:Open the ./views/index.hbs file and replace its entire contents with the following.

<div class="jumbotron">
  <h1>Node Graph Tutorial</h1>
  <p class="lead">This sample app shows how to use the Microsoft Graph API to access Outlook data from Node.js</p>
  {{#if user}}
    <p>Welcome {{user}}!</p>
  {{else}}
    <a class="btn btn-primary btn-large" href="{{signInUrl}}">Click here to login</a>
  {{/if}}
</div>

Da das vom Express-Generator erstellte Standard-CSS einen Konflikt mit Bootstrap verursacht, wollen wir es jetzt löschen.The default CSS created by the Express generator conflicts with Bootstrap, so let's remove it for now. Öffnen Sie die Datei ./public/stylesheets/style.css, und ersetzen Sie ihren Inhalt durch Folgendes:Open the ./public/stylesheets/style.css file and replace its entire contents with the following.

pre {
  word-wrap: break-word;
  white-space: pre-wrap;
}

Wenn Sie jetzt die Seite aktualisieren, müsste sie komplett anders aussehen.If you refresh the page now, it should look very different.

Screenshot der Homepage

Mit der Schaltfläche "Anmelden" ist noch keine Funktion verknüpft, dies wollen wir jetzt ändern.The sign-in button doesn't do anything yet, so let's move on to changing that.

Registrieren der AppRegister the app

Wichtig

Neue App-Registrierungen sollten im Anwendungsregistrierungsportal erstellt und verwaltet werden, damit sie mit Outlook.com kompatibel sind. Erstellen Sie neue App-Registrierungen nur dann im Azure-Verwaltungsportal Folgendes auf Ihre App zutrifft:New app registrations should be created and managed in the new Application Registration Portal to be compatible with Outlook.com. Only create new app registrations in the Azure Management Portal if your app:

  • Sie verwendet den OAuth2 Client Credentials Grant-Fluss oderUses the OAuth2 Client Credentials Grant Flow, or
  • Sie muss neben Outlook auf andere Office 365-Arbeitslasten zugreifen (z. B. OneDrive for Business oder SharePoint)Needs to access other Office 365 workloads besides Outlook (such as OneDrive for Business or SharePoint)

Beachten Sie, dass mithilfe des Azure-Verwaltungsportals registrierte Apps nicht mit Outlook.com kompatibel sind und dass Berechtigungsbereiche nicht dynamisch angefordert werden können.Bear in mind that apps registered using the Azure Management Portal will not be compatible with Outlook.com, and will not be able to dynamically request permissions scopes. Vorhandene App-Registrierungen, die im Azure-Verwaltungsportal erstellt wurden, funktionieren weiterhin nur für Office 365.Existing app registrations that were created in the Azure Management Portal will continue to work for Office 365 only. Diese Registrierungen werden nicht im Anwendungsregistrierungsportal angezeigt und müssen im Azure-Verwaltungsportal verwaltet werden.These registrations do not show up in the Application Registration Portal and must be managed in the Azure Management Portal.

KontoanforderungenAccount requirements

Um das Anwendungsregistrierungsportal zu verwenden, benötigen Sie entweder ein Office 365-Geschäfts- oder Schulkonto oder ein Microsoft-Konto. Wenn Sie nicht über eines dieser Konten verfügen, haben Sie verschiedene Möglichkeiten:In order to use the Application Registration Portal, you need either an Office 365 work or school account, or a Microsoft account. If you don't have either of these, you have a number of options:

  • Registrieren Sie sich hier für ein neues Microsoft-Konto.Sign up for a new Microsoft account here.
  • Sie haben mehrere Möglichkeiten, ein Office 365-Abonnement zu erhalten:You can obtain an Office 365 subscription in a couple of different ways:

REST-API-VerfügbarkeitREST API availability

Die REST-API ist derzeit auf allen Office 365-Konten, die über Exchange Online verfügen, sowie auf allen Outlook.com-Konten aktiviert.The REST API is currently enabled on all Office 365 accounts that have Exchange Online, and all Outlook.com accounts.

Wechseln Sie zum App-Registrierungsportal, um schnell eine App-ID und einen geheimen Schlüssel abzurufen.Head over to the Application Registration Portal to quickly get an application ID and secret.

  1. Melden Sie sich über den Link Anmelden mit Ihrem Microsoft-Konto (Outlook.com) oder Ihrem Geschäfts-, Schul- oder Unikonto (Office 365) an.Using the Sign in link, sign in with either your Microsoft account (Outlook.com), or your work or school account (Office 365).
  2. Klicken Sie auf die Schaltfläche App hinzufügen. Geben Sie node-tutorial für den Namen ein, und klicken Sie auf Anwendung erstellen.Click the Add an app button. Enter node-tutorial for the name and click Create application.
  3. Suchen Sie den Abschnitt Anwendungsgeheimnisse, und klicken Sie auf die Schaltfläche Neues Kennwort generieren. Kopieren Sie nun das Kennwort, und speichern Sie es an einem sicheren Ort. Nachdem Sie das Kennwort kopiert haben, klicken Sie auf Ok.Locate the Application Secrets section, and click the Generate New Password button. Copy the password now and save it to a safe place. Once you've copied the password, click Ok.
  4. Suchen Sie den Abschnitt Plattformen, und klicken Sie auf Plattform hinzufügen. Wählen Sie Web aus, und geben Sie dann http://localhost:3000/authorize unter Umleitungs-URIs ein.Locate the Platforms section, and click Add Platform. Choose Web, then enter http://localhost:3000/authorize under Redirect URIs.
  5. Klicken Sie auf Speichern, um die Registrierung abzuschließen. Kopieren Sie die App-ID, und speichern Sie sie zusammen mit dem Kennwort, das Sie zuvor kopiert haben. Wir benötigen diese Werte bald.Click Save to complete the registration. Copy the Application Id and save it along with the password you copied earlier. We'll need those values soon.

So sollten die Details Ihrer App-Registrierung aussehen, wenn Sie fertig sind.Here's what the details of your app registration should look like when you are done.

Screenshot der abgeschlossenen App-Registrierung im App-Registrierungsportal

Implementieren von OAuth2Implementing OAuth2

Unser Ziel in diesem Abschnitt ist es, die Schaltfläche auf unserer Homepage so einzurichten, dass der OAuth2-Autorisierungscodegenehmigungs-Fluss mit Azure AD initiiert wird.Our goal in this section is to make the button on our home page initiate the OAuth2 Authorization Code Grant flow with Azure AD. Zur Verarbeitung der OAuth-Anforderung verwenden wir die simple-oauth2-Bibliothek, und zum Laden der App-ID und des Schlüssels aus einer ENV-Datei verwenden wir die dotenv-Bibliothek.We'll use the simple-oauth2 library to handle our OAuth requests, and the dotenv library to load our app ID and secret from an ENV file. Geben Sie an der Eingabeaufforderung den folgenden Befehl ein.At your command prompt, enter the following command.

npm install dotenv simple-oauth2 --save

Erstellen Sie eine neue Datei mit dem Namen .env im Ordner node-tutorial, und fügen Sie Folgendes zu der Datei hinzu:Create a new file called .env in the node-tutorial folder, and add the following in the file.

APP_ID=YOUR APP ID HERE
APP_PASSWORD=YOUR APP PASSWORD HERE
APP_SCOPES=openid profile User.Read Mail.Read
REDIRECT_URI=http://localhost:3000/authorize

Als Erstes definieren wir die Client-ID und den geheimen Clientschlüssel. Außerdem definieren wir eine Umleitungs-URI und ein Array von Bereichen. Das Bereichsarray enthält die Bereiche openid, User.Read und Mail.Read, da die E-Mails des Benutzers nur gelesen werden sollen. Ersetzen Sie YOUR APP ID HERE durch die Anwendungs-ID und YOUR APP PASSWORD HERE durch das Kennwort, das Sie in Schritt 3 generiert haben; speichern Sie anschließend die Änderungen.The first thing we do here is define our client ID and secret. We also define a redirect URI and an array of scopes. The scope array includes the openid, User.Read, and Mail.Read scopes, since we will only read the user's mail. Replace the YOUR APP ID HERE with the application ID and YOUR APP PASSWORD HERE with the password you generated in step 3 and save your changes.

Nun fügen wir Code hinzu, damit die Datei geladen wird, wenn die App startet.Now let's add code to load this file when the app starts. Dadurch stehen die Werte in der Datei in process.env zur Laufzeit zur Verfügung.This will make the values in the file available in process.env at runtime. Öffnen Sie ./app.js und fügen Sie die folgende Zeile direkt vor der Zeile var index = require('./routes/index'); hinzu.Open ./app.js and add the following line just before the var index = require('./routes/index'); line.

require('dotenv').config();

Jetzt ist die Bibliothek installiert und kann verwendet werden.Now the library is installed and ready to use. Erstellen Sie ein neues Verzeichnis im Verzeichnis node-tutorial mit der Bezeichnung helpers, und erstellen Sie dann eine neue Datei namens auth.js in diesem Verzeichnis.Create a new directory in the node-tutorial directory called helpers, then create a new file called auth.js in that directory. Wir beginnen, indem wir zunächst eine Funktion zum Generieren der Anmelde-URL definieren.We'll start here by defining a function to generate the login URL.

Inhalte der Datei ./helpers/auth.jsContents of the ./helpers/auth.js file

const credentials = {
  client: {
    id: process.env.APP_ID,
    secret: process.env.APP_PASSWORD,
  },
  auth: {
    tokenHost: 'https://login.microsoftonline.com',
    authorizePath: 'common/oauth2/v2.0/authorize',
    tokenPath: 'common/oauth2/v2.0/token'
  }
};
const oauth2 = require('simple-oauth2').create(credentials);

function getAuthUrl() {
  const returnVal = oauth2.authorizationCode.authorizeURL({
    redirect_uri: process.env.REDIRECT_URI,
    scope: process.env.APP_SCOPES
  });
  console.log(`Generated auth url: ${returnVal}`);
  return returnVal;
}

exports.getAuthUrl = getAuthUrl;

Ändern Sie die Route in der Datei ./routes/index.js, um die Funktion getAuthUrl zum Generieren einer Anmeldungs-URL zu verwenden.Modify the route in the ./routes/index.js file to use the getAuthUrl function to generate a sign-in URL. Sie müssen die Datei auth.js anfordern, um Zugriff auf diese Funktion zu erhalten.You'll need to require the auth.js file to gain access to this function.

Aktualisierte Inhalte der Datei ./routes/index.jsUpdated contents of the ./routes/index.js file

var express = require('express');
var router = express.Router();
var authHelper = require('../helpers/auth');

/* GET home page. */
router.get('/', function(req, res, next) {
  let parms = { title: 'Home', active: { home: true } };

  parms.signInUrl = authHelper.getAuthUrl();
  parms.debug = parms.signInUrl;
  res.render('index', parms);
});

module.exports = router;

Speichern Sie Ihre Änderungen, und navigieren Sie zu http://localhost:3000.Save your changes and browse to http://localhost:3000. Wenn Sie über den Link fahren (oder sich den Wert im Feld zum Debuggen am unteren Rand der Seite betrachten), sollten Sie Folgendes sehen:If you hover over the link (or look at the value in the debug box at the bottom of the page), it should look like:

https://login.microsoftonline.com/common/oauth2/authorize?redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fauthorize&scope=openid+User.Read+Mail.Read&response_type=code&client_id=<SOME GUID>

Der Abschnitt <SOME GUID> sollte mit Ihrer Client-ID übereinstimmen. Klicken Sie auf den Link. Daraufhin sollte die Anmeldeseite angezeigt werden. Melden Sie sich mit Ihrem Office 365- oder Outlook.com-Konto an. Ihr Browser sollte nun zurück zu unserer App geleitet werden, und es sollte der folgende Fehler angezeigt werden:The <SOME GUID> portion should match your client ID. Click on the link and you should be presented with a sign in page. Sign in with your Office 365 or Outlook.com account. Your browser should redirect to back to our app, and you should see a lovely error:

404 Not Found

Grund für diesen Fehler ist, dass wir noch keine Route für das Verarbeiten des /authorize-Pfads implementiert haben, den wir als unseren Umleitungs-URI bereitgestellt haben.The reason we're seeing the error is because we haven't implemented a route to handle the /authorize path we provided as our redirect URI. Im Folgenden werden wir diesen Fehler beheben.Let's fix that error now.

Austauschen des Codes durch ein TokenExchanging the code for a token

Zunächst fügen wir eine neue Datei für unsere auth-bezogenen Routen hinzu.First, let's add a new file for our auth-related routes. Erstellen Sie eine Datei mit dem Namen authorize.js im Verzeichnis ./routes, und fügen Sie den folgenden Code hinzu:Create a file called authorize.js in the ./routes directory and add the following code.

Hinweis

Beim Erstellen der Datei können Sie die Datei users.js löschen, da sie nicht für dieses Lernprogramm benötigt wird.While you're creating that file, you can also delete users.js which is not needed for this tutorial. Dieser Schritt ist optional.This is optional.

Inhalte von ./routes/authorize.jsContents of ./routes/authorize.js

var express = require('express');
var router = express.Router();
var authHelper = require('../helpers/auth');

/* GET /authorize. */
router.get('/', function(req, res, next) {
  // Get auth code
  const code = req.query.code;

  // If code is present, use it
  if (code) {
    res.render('index', { title: 'Home', debug: `Auth code: ${code}` });
  } else {
    // Otherwise complain
    res.render('error', { title: 'Error', message: 'Authorization error', error: { status: 'Missing code parameter' } });
  }
});

module.exports = router;

Jetzt fügen wir die Datei zu den Routen der Anwendung hinzu.Now let's add this file to the app's routes. Öffnen Sie die Datei ./app.js, und fügen Sie die folgende Zeile nach der Zeile var index = require('./routes/index'); hinzu:Open the ./app.js file and add the following line after the var index = require('./routes/index'); line.

var authorize = require('./routes/authorize');

Fügen Sie dann die folgende Zeile nach der Zeile app.use('/', index); hinzu:Then add the following line after the app.use('/', index); line.

app.use('/authorize', authorize);

Hinweis

Wenn Sie die ./routes/users.js-Datei gelöscht haben, müssen Sie unbedingt die folgenden Zeilen aus `./app.js. entfernen,If you deleted the ./routes/users.js file, be sure to remove the following lines from `./app.js. um Fehler zu vermeiden.to avoid errors.

var users = require('./routes/users');
app.use('/users', users);

Starten Sie den Node-Server neu, und aktualisieren Sie Ihren Browser (oder wiederholen Sie den Anmeldevorgang). Anstelle eines Fehlers sollte nun der Wert des Autorisierungscodes auf dem Bildschirm angezeigt werden. Das ist zwar schon besser, aber immer noch nicht sehr hilfreich. Lassen Sie uns nun tatsächlich etwas mit dem Code ausführen.Restart the Node server and refresh your browser (or repeat the sign-in process). Now instead of an error, you should see the value of the authorization code printed on the screen. We're getting closer, but that's still not very useful. Let's actually do something with that code.

Wir fügen eine weitere Hilfsfunktion mit dem Namen getTokenFromCode zu ./helpers/auth.js hinzu.Let's add another helper function to ./helpers/auth.js called getTokenFromCode.

getTokenFromCode in ./helpers/auth.jsgetTokenFromCode in ./helpers/auth.js

async function getTokenFromCode(auth_code) {
  let result = await oauth2.authorizationCode.getToken({
    code: auth_code,
    redirect_uri: process.env.REDIRECT_URI,
    scope: process.env.APP_SCOPES
  });

  const token = oauth2.accessToken.create(result);
  console.log('Token created: ', token.token);
  return token.token.access_token;
}

exports.getTokenFromCode = getTokenFromCode;

Nun möchten wir sicherstellen, dass diese funktioniert.Let's make sure that works. Ändern Sie die authorize-Funktion in der Datei ./routes/authorize.js, um diese Hilfsfunktion zu verwenden und den Rückgabewert anzuzeigen.Modify the authorize function in ./routes/authorize.js file to use this helper function and display the return value.

Aktualisierte Route in ./routes/authorize.jsUpdated route in ./routes/authorize.js

router.get('/', async function(req, res, next) {
  // Get auth code
  const code = req.query.code;

  // If code is present, use it
  if (code) {
    let token;

    try {
      token = await authHelper.getTokenFromCode(code);
    } catch (error) {
      res.render('error', { title: 'Error', message: 'Error exchanging code for token', error: error });
    }

    res.render('index', { title: 'Home', debug: `Access token: ${token}` });
  } else {
    // Otherwise complain
    res.render('error', { title: 'Error', message: 'Authorization error', error: { status: 'Missing code parameter' } });
  }
});

Wenn Sie Ihre Änderungen speichern, den Server neu starten und den Anmeldevorgang erneut durchlaufen, sollte eine lange Zeichenfolge von scheinbar unsinnigen Zeichen im Debug-Fenster angezeigt werden.If you save your changes, restart the server, and go through the sign-in process again, you should now see long string of seemingly nonsensical characters in the debug window. Wenn alles nach Plan läuft, sollte dies ein Zugriffstoken sein.If everything's gone according to plan, that should be an access token.

Im Folgenden ändern wir unseren Code so, dass das Token nicht angezeigt, sondern in einem Sitzungscookie gespeichert wird.Now let's change our code to store the token in a session cookie instead of displaying it. Verwenden wir nun auch das Identitätstoken, um den Namen des angemeldeten Benutzers abzurufen.While we're at it, let's also make use of the identity token to get the logged on user's name. Das Identitätstoken ist ein JSON-Web-Token (JWT). Installieren wir also die jsonwebtoken-Bibliothek, um es zu analysieren.The identity token is a JSON web token (JWT), so let's install the jsonwebtoken library to parse it. Öffnen Sie die Shell, und führen Sie den folgenden Befehl aus:Open your shell and run the following.

npm install jsonwebtoken --save

Fügen Sie die folgende Zeile oben in ./helpers/auth.js ein:Add the following line to the top of ./helpers/auth.js:

const jwt = require('jsonwebtoken');

Jetzt aktualisieren wir die Methode getTokenFromCode so, dass sie das Token und den Benutzernamen in der Sitzung speichert.Now let's update the getTokenFromCode method so that it will save the token and user name in the session. Dazu benötigen wir Zugriff auf die Cookies. Aktualisieren wir also die Methode um einen zusätzlichen Parameter, das Express-Antwortobjekt.To do that we need access to the cookies, so update the method to take an additional parameter, the Express Response object.

Aktualisierte getTokenFromCode in ./helpers/auth.jsUpdated getTokenFromCode in ./helpers/auth.js

async function getTokenFromCode(auth_code, res) {
  let result = await oauth2.authorizationCode.getToken({
    code: auth_code,
    redirect_uri: process.env.REDIRECT_URI,
    scope: process.env.APP_SCOPES
  });

  const token = oauth2.accessToken.create(result);
  console.log('Token created: ', token.token);

  saveValuesToCookie(token, res);

  return token.token.access_token;
}

Fügen Sie dann die saveValuesToCookie-Methode in ./helpers/auth.js hinzu.Then add the saveValuesToCookie method in ./helpers/auth.js.

saveValuesToCookie in ./helpers/auth.jssaveValuesToCookie in ./helpers/auth.js

function saveValuesToCookie(token, res) {
  // Parse the identity token
  const user = jwt.decode(token.token.id_token);

  // Save the access token in a cookie
  res.cookie('graph_access_token', token.token.access_token, {maxAge: 3600000, httpOnly: true});
  // Save the user's name in a cookie
  res.cookie('graph_user_name', user.name, {maxAge: 3600000, httpOnly: true});
}

Aktualisieren Sie nun die Route in ./routes/authorize.js so, dass sie das Express-Antwortobjekt an getTokenFromCode übergibt und wieder zur Startseite umleitet.Now update the route in ./routes/authorize.js to pass the Express response object to getTokenFromCode and redirect back to the home page.

Aktualisierte Route in ./routes/authorize.jsUpdated route in ./routes/authorize.js

router.get('/', async function(req, res, next) {
  // Get auth code
  const code = req.query.code;

  // If code is present, use it
  if (code) {
    let token;

    try {
      token = await authHelper.getTokenFromCode(code, res);
    } catch (error) {
      res.render('error', { title: 'Error', message: 'Error exchanging code for token', error: error });
    }

    // Redirect to home
    res.redirect('/');
  } else {
    // Otherwise complain
    res.render('error', { title: 'Error', message: 'Authorization error', error: { status: 'Missing code parameter' } });
  }
});

Jetzt möchten wir sicherstellen, dass die Homepage tatsächlich wiedergibt, dass der Benutzer angemeldet ist.Now let's make the home page actually reflect the fact that the user is signed in. Aktualisieren Sie die Route in ./routes/index.js, um zu prüfen, ob sich ein Token und ein Benutzername in den Sitzungscookies befinden und diese entsprechend reagieren.Update the route in ./routes/index.js to check if there's a token and user name in session cookies and react accordingly.

Aktualisierte Route in ./routes/index.jsUpdated route in ./routes/index.js

router.get('/', function(req, res, next) {
  let parms = { title: 'Home', active: { home: true } };

  const accessToken = req.cookies.graph_access_token;
  const userName = req.cookies.graph_user_name;

  if (accessToken && userName) {
    parms.user = userName;
    parms.debug = `User: ${userName}\nAccess Token: ${accessToken}`;
  } else {
    parms.signInUrl = authHelper.getAuthUrl();
    parms.debug = parms.signInUrl;
  }

  res.render('index', parms);
});

Starten Sie die App neu.Restart the app. Wenn Sie sich nun anmelden, sollten Sie auf die Startseite umgeleitet werden, wobei der Name des angemeldeten Benutzers in der Navigationsleiste und im Textkörper der Seite angezeigt wird.Now if you sign-in, you should get redirected back to the home page with the signed-in user's name displayed in the nav bar and the body of the page.

AbmeldenSigning out

Da wir jetzt das Token des Benutzers in einem Sitzungscookie speichern, möchten wir eine Methode zum Abmelden bereitstellen. Fügen Sie eine neue clearCookies-Methode in ./helpers/auth.js hinzu.Since we're now saving the user's token in a session cookie, let's provide a method to sign out. Let's add a new method clearCookies in ./helpers/auth.js.

clearCookies in ./helpers/auth.jsclearCookies in ./helpers/auth.js

function clearCookies(res) {
  // Clear cookies
  res.clearCookie('graph_access_token', {maxAge: 3600000, httpOnly: true});
  res.clearCookie('graph_user_name', {maxAge: 3600000, httpOnly: true});
}

exports.clearCookies = clearCookies;

Fügen Sie eine neue Route in ./routes/authorize.js hinzu, die diese Methode aufruft.Add a new route in ./routes/authorize.js that calls this method.

/* GET /authorize/signout */
router.get('/signout', function(req, res, next) {
  authHelper.clearCookies(res);

  // Redirect to home
  res.redirect('/');
});

Nach einem Neustart der App sollten Sie sich jetzt über das Menü in der oberen rechten Ecke an- und abmelden können.After restarting the app, you should now be able to sign in and out using the menu in the top right-hand corner.

Aktualisieren des ZugriffstokensRefreshing the access token

Von Azure zurückgegebene Zugriffstoken sind eine Stunde lang gültig. Wenn Sie das Token verwenden, nachdem es abgelaufen ist, geben die API-Aufrufe 401-Fehler zurück. Sie könnten den Benutzer bitten, sich erneut anzumelden, aber die bessere Option besteht darin, das Token automatisch zu aktualisieren.Access tokens returned from Azure are valid for an hour. If you use the token after it has expired, the API calls will return 401 errors. You could ask the user to sign in again, but the better option is to refresh the token silently.

Zu diesem Zweck muss die App den offline_access-Bereich anfordern.In order to do that, the app must request the offline_access scope. Fügen Sie diesen Bereich dem APP_SCOPES-Wert in .env hinzu.Add this scope to the APP_SCOPES value in .env.

APP_SCOPES=openid profile offline_access User.Read Mail.Read

Dies bewirkt, dass die Tokenantwort von Azure ein Aktualisierungstoken enthält.This will cause the token response from Azure to include a refresh token. Wir werden nun die saveValuesToCookie-Methode in ./helpers/auth.js so aktualisieren, dass das Aktualisierungstoken und die Ablaufzeit in einem Sitzungscookie gespeichert werden.Let's update the saveValuesToCookie method in ./helpers/auth.js to save the refresh token and the expiration time in a session cookie.

Aktualisierte saveValuesToCookie in ./helpers/auth.jsUpdated saveValuesToCookie in ./helpers/auth.js

function saveValuesToCookie(token, res) {
  // Parse the identity token
  const user = jwt.decode(token.token.id_token);

  // Save the access token in a cookie
  res.cookie('graph_access_token', token.token.access_token, {maxAge: 3600000, httpOnly: true});
  // Save the user's name in a cookie
  res.cookie('graph_user_name', user.name, {maxAge: 3600000, httpOnly: true});
  // Save the refresh token in a cookie
  res.cookie('graph_refresh_token', token.token.refresh_token, {maxAge: 7200000, httpOnly: true});
  // Save the token expiration time in a cookie
  res.cookie('graph_token_expires', token.token.expires_at.getTime(), {maxAge: 3600000, httpOnly: true});
}

Aktualisieren wir nun auch clearCookies, um diese neuen Werte zu entfernen.Let's also update clearCookies to remove these new values.

Aktualisierte clearCookies in ./helpers/auth.jsUpdated clearCookies in ./helpers/auth.js

function clearCookies(res) {
  // Clear cookies
  res.clearCookie('graph_access_token', {maxAge: 3600000, httpOnly: true});
  res.clearCookie('graph_user_name', {maxAge: 3600000, httpOnly: true});
  res.clearCookie('graph_refresh_token', {maxAge: 7200000, httpOnly: true});
  res.clearCookie('graph_token_expires', {maxAge: 3600000, httpOnly: true});
}

Fügen wir nun eine Hilfsfunktion in ./helpers/auth.js hinzu, um das zwischengespeicherte Token abzurufen, zu überprüfen, ob es abgelaufen ist, und es ggf. zu aktualisieren.Now let's add a helper function in ./helpers/auth.js to retrieve the cached token, check if it is expired, and refresh it if so.

getAccessToken in ./helpers/auth.jsgetAccessToken in ./helpers/auth.js

async function getAccessToken(cookies, res) {
  // Do we have an access token cached?
  let token = cookies.graph_access_token;

  if (token) {
    // We have a token, but is it expired?
    // Expire 5 minutes early to account for clock differences
    const FIVE_MINUTES = 300000;
    const expiration = new Date(parseFloat(cookies.graph_token_expires - FIVE_MINUTES));
    if (expiration > new Date()) {
      // Token is still good, just return it
      return token;
    }
  }

  // Either no token or it's expired, do we have a
  // refresh token?
  const refresh_token = cookies.graph_refresh_token;
  if (refresh_token) {
    const newToken = await oauth2.accessToken.create({refresh_token: refresh_token}).refresh();
    saveValuesToCookie(newToken, res);
    return newToken.token.access_token;
  }

  // Nothing in the cookies that helps, return empty
  return null;
}

exports.getAccessToken = getAccessToken;

Im letzten Schritt aktualisieren wir die Route in ./routes/index.js so, dass sie diese Funktion verwendet.Finally, let's update the route in ./routes/index.js to use this function.

Aktualisierte Route in ./routes/index.jsUpdated route in ./routes/index.js

router.get('/', async function(req, res, next) {
  let parms = { title: 'Home', active: { home: true } };

  const accessToken = await authHelper.getAccessToken(req.cookies, res);
  const userName = req.cookies.graph_user_name;

  if (accessToken && userName) {
    parms.user = userName;
    parms.debug = `User: ${userName}\nAccess Token: ${accessToken}`;
  } else {
    parms.signInUrl = authHelper.getAuthUrl();
    parms.debug = parms.signInUrl;
  }

  res.render('index', parms);
});

Verwenden der Mail-APIUsing the Mail API

Da wir nun ein Zugriffstoken abrufen können, bietet es sich an, mit der Mail-API fortzufahren.Now that we can get an access token, we're in a good position to do something with the Mail API.

Um die Microsoft Graph-API zu verwenden, installieren Sie die Microsoft Graph-JavaScript-Clientbibliothek über die Befehlszeile.To use the Microsoft Graph API, install the Microsoft Graph JavaScript Client Library from the command line.

npm install @microsoft/microsoft-graph-client --save

Beginnen wir mit dem Erstellen einer mail-Route.Let's start by creating a mail route. Erstellen Sie eine Datei mit dem Namen mail.js im Verzeichnis ./routes, und fügen Sie den folgenden Code hinzu:Create a file called mail.js in the ./routes directory and add the following code.

Inhalte von ./routes/mail.jsContents of ./routes/mail.js

var express = require('express');
var router = express.Router();
var authHelper = require('../helpers/auth');
var graph = require('@microsoft/microsoft-graph-client');

/* GET /mail */
router.get('/', async function(req, res, next) {
  let parms = { title: 'Inbox', active: { inbox: true } };

  const accessToken = await authHelper.getAccessToken(req.cookies, res);
  const userName = req.cookies.graph_user_name;

  if (accessToken && userName) {
    parms.user = userName;

    // Initialize Graph client
    const client = graph.Client.init({
      authProvider: (done) => {
        done(null, accessToken);
      }
    });

    try {
      // Get the 10 newest messages from inbox
      const result = await client
      .api('/me/mailfolders/inbox/messages')
      .top(10)
      .select('subject,from,receivedDateTime,isRead')
      .orderby('receivedDateTime DESC')
      .get();

      parms.debug = `Graph request returned: ${JSON.stringify(result, null, 2)}`;
    } catch (err) {
      parms.message = 'Error retrieving messages';
      parms.error = { status: `${err.code}: ${err.message}` };
      parms.debug = JSON.stringify(err.body, null, 2);
      res.render('error', parms);
    }

  } else {
    // Redirect to home
    res.redirect('/');
  }

  res.render('index', parms);
});

module.exports = router;

Zusammenfassung des neuen Codes:To summarize the new code:

  • Es wird ein Graph-Clientobjekt erstellt und initialisiert, um das Zugriffstoken zu verwenden.It creates a Graph client object and initializes it to use the access token.
  • Die /me/mailfolders/inbox/messages-API wird aufgerufen, um die Posteingangsnachrichten abzurufen, und es werden weitere Methoden zum Steuern der Anforderung verwendet:It calls the /me/mailfolders/inbox/messages API to get inbox messages, and uses other methods to control the request:
    • Die top-Methode wird mit dem Wert 10 verwendet, um die Ergebnisse auf die ersten 10 einzuschränken.It uses the top method with a value of 10 to limit the results to the first 10.
    • Die select-Methode wird verwendet, um nur die Eigenschaften subject, from, receivedDateTime und isRead anzufordern.It uses the select method to only request the subject, from, receivedDateTime, and isRead properties.
    • Die orderby-Methode wird mit dem Wert receivedDateTime DESC verwendet, um die neuesten Nachrichten zuerst abzurufen.It uses the orderby method with a value of receivedDateTime DESC to get the newest messages first.

Jetzt fügen wir diese neue Route zu der Anwendung hinzu.Now let's add this new route to the app. Öffnen Sie die Datei ./app.js, und nehmen Sie die folgenden Änderungen vor:Open the ./app.js file and make the following changes:

  • Fügen Sie var mail = require('./routes/mail'); nach der Zeile var authorize = require('./routes/authorize'); hinzu.Add var mail = require('./routes/mail'); after the var authorize = require('./routes/authorize'); line.
  • Fügen Sie app.use('/mail', mail); nach der Zeile app.use('/authorize', authorize); hinzu.Add app.use('/mail', mail); after the app.use('/authorize', authorize); line.

Speichern Sie die Änderungen, und starten Sie die App neu.Save your changes and restart the app. Navigieren Sie zur Homepage, und melden Sie sich an.Browse to the home page and sign in. Klicken Sie auf das Posteingang-Element in der Navigationsleiste.Click the Inbox nav bar item. Im Debug-Fenster wird eine JSON-Antwort mit den von der API zurückgegebenen Nachrichten angezeigt.You should see a JSON response in the Debug window listing the messages returned by the API.

Anzeigen der ErgebnisseDisplaying the results

Da wir jetzt Nachrichten von der API abrufen können, sollten wir eine Ansicht zu der Anwendung hinzufügen, um diese korrekt anzuzeigen.Now that we can get messages from the API, let's add a view to the app to display them properly. Erstellen Sie eine Datei mit dem Namen mail.hbs im Ordner ./views, und fügen Sie den folgenden Code hinzu.Create a file called mail.hbs in the ./views folder and add the following code.

<h2>Inbox</h2>
<table class="table">
  <thead class="thead-light">
    <th scope="col">From</th>
    <th scope="col">Subject</th>
    <th scope="col">Received</th>
  </thead>
  <tbody>
    {{#each messages}}
      <tr class="{{#unless this.isRead}}table-primary{{/unless}}">
        <td title="{{this.from.emailAddress.address}}">{{this.from.emailAddress.name}}</td>
        <td>{{this.subject}}</td>
        <td>{{this.receivedDateTime}}</td>
      </tr>
    {{/each}}
  </tbody>
</table>

Ändern Sie jetzt die Route in ./routes/mail.js, damit das Array von Nachrichten im messages-Parameter weitergegeben und die neue E-Mail-Ansicht gerendert wird, statt die Homepage wiederzuverwenden.Now modify the route in ./routes/mail.js to pass the array of messages in the messages parameter, and to render the new mail view rather than reuse the home page.

Aktualisierte Route in ./routes/mail.jsUpdated route in ./routes/mail.js

router.get('/', async function(req, res, next) {
  let parms = { title: 'Inbox', active: { inbox: true } };

  const accessToken = await authHelper.getAccessToken(req.cookies, res);
  const userName = req.cookies.graph_user_name;

  if (accessToken && userName) {
    parms.user = userName;

    // Initialize Graph client
    const client = graph.Client.init({
      authProvider: (done) => {
        done(null, accessToken);
      }
    });

    try {
      // Get the 10 newest messages from inbox
      const result = await client
      .api('/me/mailfolders/inbox/messages')
      .top(10)
      .select('subject,from,receivedDateTime,isRead')
      .orderby('receivedDateTime DESC')
      .get();

      parms.messages = result.value;
      res.render('mail', parms);
    } catch (err) {
      parms.message = 'Error retrieving messages';
      parms.error = { status: `${err.code}: ${err.message}` };
      parms.debug = JSON.stringify(err.body, null, 2);
      res.render('error', parms);
    }

  } else {
    // Redirect to home
    res.redirect('/');
  }
});

Starten Sie den Server neu, melden Sie sich an, und klicken Sie auf das Posteingang-Navigationselement.Restart the server, sign in, and click the Inbox nav item. Es wird eine formatierte Tabelle mit den Ergebnissen angezeigt.You should see a formatted table of the results.

Eine HTML-Tabelle mit dem Inhalt eines Posteingangs.

Hinzufügen von Kalender- und Kontakte-APIsAdding Calendar and Contacts APIs

Da Sie nun das Aufrufen der Outlook-Mail-API gemeistert haben, dürfte es kein Problem mehr sein, das gleiche für Kalender- und Kontakte-APIs zu tun.Now that you've mastered calling the Outlook Mail API, doing the same for Calendar and Contacts APIs is similar and easy.

Tipp

Wenn Sie die Schritte in diesem Lernprogramm befolgt haben, haben Sie wahrscheinlich ein Zugriffstoken in Ihrem Sitzungscookie gespeichert.If you've followed along with the tutorial, you probably have an access token saved in your session cookie. Dieses Token ist nur für den Mail.Read-Bereich gültig.That token will only be valid for the Mail.Read scope. Um die Kalender- oder Kontakte-API aufzurufen, müssen wir neue Bereiche hinzufügen.In order to call the Calendar or Contacts API, we will need to add new scopes. Melden Sie sich unbedingt ab, damit Sie den Anmeldevorgang von vorne beginnen können, um ein neues Zugriffstoken zu erhalten.Be sure to sign out so that you can start the login process from the beginning to get a new access token.

Für die Kalender-API:For Calendar API:

  1. Aktualisieren Sie den Wert APP_SCOPES in .env, um den Calendars.Read-Bereich einzuschließen.Update the APP_SCOPES value in .env to include the Calendars.Read scope.

    APP_SCOPES=openid profile offline_access User.Read Mail.Read Calendars.Read
    
  2. Fügen Sie eine calendar-Route in ./app.js hinzu.Add a calendar route in ./app.js.

    var calendar = require('./routes/calendar');
    app.use('/calendar', calendar);
    
  3. Fügen Sie ein Kalender-Element in der Navigationsleiste ./views/layout.hbs direkt hinter dem vorhandenen Posteingang-Element hinzu.Add a Calendar nav bar item in ./views/layout.hbs, just after the existing Inbox item.

    {{#if user}}
      <li class="nav-item{{#if active.inbox}} active{{/if}}">
        <a class="nav-link" href="/mail">Inbox</a>
      </li>
      <li class="nav-item{{#if active.calendar}} active{{/if}}">
        <a class="nav-link" href="/calendar">Calendar</a>
      </li>
    {{/if}}
    
  4. Fügen Sie eine calendar.js-Datei im Ordner ./routes/ hinzu, und fügen Sie den folgenden Code hinzu.Add a calendar.js file in the ./routes/ folder and add the following code.

    var express = require('express');
    var router = express.Router();
    var authHelper = require('../helpers/auth');
    var graph = require('@microsoft/microsoft-graph-client');
    
    /* GET /calendar */
    router.get('/', async function(req, res, next) {
      let parms = { title: 'Calendar', active: { calendar: true } };
    
      const accessToken = await authHelper.getAccessToken(req.cookies, res);
      const userName = req.cookies.graph_user_name;
    
      if (accessToken && userName) {
        parms.user = userName;
    
        // Initialize Graph client
        const client = graph.Client.init({
          authProvider: (done) => {
            done(null, accessToken);
          }
        });
    
        // Set start of the calendar view to today at midnight
        const start = new Date(new Date().setHours(0,0,0));
        // Set end of the calendar view to 7 days from start
        const end = new Date(new Date(start).setDate(start.getDate() + 7));
    
        try {
          // Get the first 10 events for the coming week
          const result = await client
          .api(`/me/calendarView?startDateTime=${start.toISOString()}&endDateTime=${end.toISOString()}`)
          .top(10)
          .select('subject,start,end,attendees')
          .orderby('start/dateTime DESC')
          .get();
    
          parms.events = result.value;
          res.render('calendar', parms);
        } catch (err) {
          parms.message = 'Error retrieving events';
          parms.error = { status: `${err.code}: ${err.message}` };
          parms.debug = JSON.stringify(err.body, null, 2);
          res.render('error', parms);
        }
    
      } else {
        // Redirect to home
        res.redirect('/');
      }
    });
    
    module.exports = router;
    
  5. Fügen Sie eine calendar.hbs-Datei im Ordner ./views/ hinzu, und fügen Sie den folgenden Code hinzu.Add a calendar.hbs file in the ./views/ folder and add the following code.

    <h2>Calendar</h2>
    <table class="table">
      <thead class="thead-light">
        <th scope="col">Subject</th>
        <th scope="col">Start</th>
        <th scope="col">End</th>
        <th scope="col">Attendees</th>
      </thead>
      <tbody>
        {{#each events}}
          <tr>
            <td>{{this.subject}}</td>
            <td>{{this.start.dateTime}} ({{this.start.timeZone}})</td>
            <td>{{this.end.dateTime}} ({{this.end.timeZone}})</td>
            <td>
              <ul class="list-unstyled">
                {{#each this.attendees}}
                  <li class="border p-1" title="{{this.emailAddress.address}}">
                    {{this.emailAddress.name}}
                  </li>
                {{/each}}
              </ul>
            </td>
          </tr>
        {{/each}}
      </tbody>
    </table>
    
  6. Starten Sie die App neu.Restart the app. Klicken Sie nach der Anmeldung auf das Element Kalender in der Navigationsleiste.After signing in, click the Calendar nav item.

Für die Kontakte-API:For Contacts API:

  1. Aktualisieren Sie den Wert APP_SCOPES in .env, um den Contacts.Read-Bereich einzuschließen.Update the APP_SCOPES value in .env to include the Contacts.Read scope.

    APP_SCOPES=openid profile offline_access User.Read Mail.Read Contacts.Read
    
  2. Fügen Sie eine contacts-Route in ./app.js hinzu.Add a contacts route in ./app.js.

    var contacts = require('./routes/contacts');
    app.use('/contacts', contacts);
    
  3. Fügen Sie ein Kontakte-Element in der Navigationsleiste ./views/layout.hbs direkt hinter dem vorhandenen Posteingang-Element hinzu.Add a Contacts nav bar item in ./views/layout.hbs, just after the existing Inbox item.

    {{#if user}}
      <li class="nav-item{{#if active.inbox}} active{{/if}}">
        <a class="nav-link" href="/mail">Inbox</a>
      </li>
      <li class="nav-item{{#if active.contacts}} active{{/if}}">
        <a class="nav-link" href="/contacts">Contacts</a>
      </li>
    {{/if}}
    
  4. Fügen Sie eine contacts.js-Datei im Ordner ./routes/ hinzu, und fügen Sie den folgenden Code hinzu.Add a contacts.js file in the ./routes/ folder and add the following code.

    var express = require('express');
    var router = express.Router();
    var authHelper = require('../helpers/auth');
    var graph = require('@microsoft/microsoft-graph-client');
    
    /* GET /contacts */
    router.get('/', async function(req, res, next) {
      let parms = { title: 'Contacts', active: { contacts: true } };
    
      const accessToken = await authHelper.getAccessToken(req.cookies, res);
      const userName = req.cookies.graph_user_name;
    
      if (accessToken && userName) {
        parms.user = userName;
    
        // Initialize Graph client
        const client = graph.Client.init({
          authProvider: (done) => {
            done(null, accessToken);
          }
        });
    
        try {
          // Get the first 10 contacts in alphabetical order
          // by given name
          const result = await client
          .api('/me/contacts')
          .top(10)
          .select('givenName,surname,emailAddresses')
          .orderby('givenName ASC')
          .get();
    
          parms.contacts = result.value;
          res.render('contacts', parms);
        } catch (err) {
          parms.message = 'Error retrieving contacts';
          parms.error = { status: `${err.code}: ${err.message}` };
          parms.debug = JSON.stringify(err.body, null, 2);
          res.render('error', parms);
        }
    
      } else {
        // Redirect to home
        res.redirect('/');
      }
    });
    
    module.exports = router;
    
  5. Fügen Sie eine contacts.hbs-Datei im Ordner ./views/ hinzu, und fügen Sie den folgenden Code hinzu.Add a contacts.hbs file in the ./views/ folder and add the following code.

    <h2>Contacts</h2>
    <table class="table">
      <thead class="thead-light">
        <th scope="col">First name</th>
            <th scope="col">Last name</th>
            <th scope="col">Email addresses</th>
      </thead>
      <tbody>
        {{#each contacts}}
          <tr>
            <td>{{this.givenName}}</td>
            <td>{{this.surname}}</td>
            <td>
              <ul class="list-unstyled">
                {{#each this.emailAddresses}}
                  <li class="border p-1">
                    {{this.address}}
                  </li>
                {{/each}}
              </ul>
            </td>
          </tr>
        {{/each}}
      </tbody>
    </table>
    
  6. Starten Sie die App neu.Restart the app. Klicken Sie nach der Anmeldung auf das Element Kontakte in der Navigationsleiste.After signing in, click the Contacts nav item.