让用户登录并从 JavaScript 单页应用程序 (SPA) 调用 Microsoft 图形 APISign in users and call the Microsoft Graph API from a JavaScript single-page application (SPA)

本指南演示 JavaScript 单页应用程序 (SPA) 如何执行以下操作:This guide demonstrates how a JavaScript single-page application (SPA) can:

  • 登录工作和学校帐户Sign in work and school accounts
  • 获取访问令牌Acquire an access token
  • 从 Microsoft 标识平台终结点调用需要访问令牌的 Microsoft Graph API 或其他 APICall the Microsoft Graph API or other APIs that require access tokens from the Microsoft identity platform endpoint

提示

本教程使用 MSAL.js v1.x,它仅限于对单页应用程序使用隐式授权流。This tutorial uses MSAL.js v1.x which is limited to using the implicit grant flow for single-page applications. 建议所有新的应用程序改用 MSAL.js 2.x and the authorization code flow with PKCE and CORS 支持。We recommend all new applications instead use MSAL.js 2.x and the authorization code flow with PKCE and CORS support.

本指南生成的示例应用的工作原理How the sample app generated by this guide works

显示本教程生成的示例应用的工作原理

详细信息More information

本指南创建的示例应用程序允许 JavaScript SPA 查询从 Microsoft 标识平台终结点接受令牌的 Microsoft 图形 API 或 Web API。The sample application created by this guide enables a JavaScript SPA to query the Microsoft Graph API or a web API that accepts tokens from the Microsoft identity platform endpoint. 在此方案中,用户登录后请求了访问令牌,并通过授权标头将其添加到 HTTP 请求。In this scenario, after a user signs in, an access token is requested and added to HTTP requests through the authorization header. 此令牌将用于通过 MS Graph API 获取用户的个人资料和邮件。This token will be used to acquire the user's profile and mails via MS Graph API. 令牌获取和更新由适用于 JavaScript 的 Microsoft 身份验证库 (MSAL) 处理。Token acquisition and renewal are handled by the Microsoft Authentication Library (MSAL) for JavaScript.

Libraries

本指南使用以下库:This guide uses the following library:

Library 说明Description
msal.jsmsal.js 适用于 JavaScript 的 Microsoft 身份验证库Microsoft Authentication Library for JavaScript

设置 Web 服务器或项目Set up your web server or project

想要改为下载此示例的项目?Prefer to download this sample's project instead? 下载项目文件Download the project files.

若要在执行代码示例之前对其进行配置,请跳到配置步骤To configure the code sample before you execute it, skip to the configuration step.

先决条件Prerequisites

  • 若要运行本教程,需要将本地 Web 服务器(如 Node.js.NET Core 或 IIS Express)与 Visual Studio 2017 集成。To run this tutorial, you need a local web server, such as Node.js, .NET Core, or IIS Express integration with Visual Studio 2017.

  • 本指南中的说明基于 Node.js 中生成的 Web 服务器。Instructions in this guide are based on a web server built in Node.js. 建议使用 Visual Studio Code 作为集成开发环境 (IDE)。We recommend using Visual Studio Code as your integrated development environment (IDE).

  • 新式 Web 浏览器。A modern web browser. 此 JavaScript 示例使用 ES6 约定,因此不支持 Internet Explorer 。This JavaScript sample uses ES6 conventions, and as such, it does not support Internet Explorer.

创建项目Create your project

确保已安装 Node.js,然后创建一个用于托管应用程序的文件夹。Make sure you have Node.js installed, and then create a folder to host your application. 我们将在此处实现一个简单的 Express Web 服务器来为 index.html 文件提供服务。There, we will implement a simple Express web server to serve your index.html file.

  1. 使用终端(如 Visual Studio Code 集成终端)找到项目文件夹,然后键入:Using a terminal (such as Visual Studio Code integrated terminal), locate your project folder, then type:

    npm init
    
  2. 接下来,安装必需的依赖项:Next, install the required dependencies:

    npm install express --save
    npm install morgan --save
    
  3. 现在,创建一个名为 index.js 的 .js 文件并添加以下代码:Now, create a .js file named index.js, and then add the following code:

    const express = require('express');
    const morgan = require('morgan');
    const path = require('path');
    
    //initialize express.
    const app = express();
    
    // Initialize variables.
    const port = 3000; // process.env.PORT || 3000;
    
    // Configure morgan module to log all requests.
    app.use(morgan('dev'));
    
    // Set the front-end folder to serve public assets.
    app.use(express.static('JavaScriptSPA'))
    
    // Set up a route for index.html.
    app.get('*', function (req, res) {
        res.sendFile(path.join(__dirname + '/index.html'));
    });
    
    // Start the server.
    app.listen(port);
    console.log('Listening on port ' + port + '...');
    

现在,已有一个可为 SPA 提供服务的简单服务器。You now have a simple server to serve your SPA. 在本教程结束时,所需的文件夹结构如下:The intended folder structure at the end of this tutorial is as follows:

所需 SPA 文件夹结构的文本说明

创建 SPA UICreate the SPA UI

  1. 为 JavaScript SPA 创建 index.html 文件。Create an index.html file for your JavaScript SPA. 此文件实现通过 Bootstrap 4 Framework 生成的 UI,并导入用于配置、身份验证和 API 调用的脚本文件。This file implements a UI built with Bootstrap 4 Framework and imports script files for configuration, authentication and API call.

    index.html 文件中添加以下代码:In the index.html file, add the following code:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
        <title>Quickstart | MSAL.JS Vanilla JavaScript SPA</title>
    
        <!-- msal.js with a fallback to backup CDN -->
        <script type="text/javascript" src="https://alcdn.msauth.net/lib/1.2.1/js/msal.js" integrity="sha384-9TV1245fz+BaI+VvCjMYL0YDMElLBwNS84v3mY57pXNOt6xcUYch2QLImaTahcOP" crossorigin="anonymous"></script>
        <script type="text/javascript">
          if(typeof Msal === 'undefined')document.write(unescape("%3Cscript src='https://alcdn.msftauth.net/lib/1.2.1/js/msal.js' type='text/javascript' integrity='sha384-m/3NDUcz4krpIIiHgpeO0O8uxSghb+lfBTngquAo2Zuy2fEF+YgFeP08PWFo5FiJ' crossorigin='anonymous'%3E%3C/script%3E"));
        </script>
    
        <!-- adding Bootstrap 4 for UI components  -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
      </head>
      <body>
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
          <a class="navbar-brand" href="/">MS Identity Platform</a>
          <div class="btn-group ml-auto dropleft">
            <button type="button" id="signIn" class="btn btn-secondary" onclick="signIn()">Sign In</button>
            <button type="button" id="signOut" class="btn btn-success d-none" onclick="signOut()">Sign Out</button>
        </div>
        </nav>
        <br>
        <h5 class="card-header text-center">Vanilla JavaScript SPA calling MS Graph API with MSAL.JS</h5>
        <br>
        <div class="row" style="margin:auto" >
        <div id="card-div" class="col-md-3 d-none">
        <div class="card text-center">
          <div class="card-body">
            <h5 class="card-title" id="welcomeMessage">Please sign-in to see your profile and read your mails</h5>
            <div id="profile-div"></div>
            <br>
            <br>
            <button class="btn btn-primary" id="seeProfile" onclick="seeProfile()">See Profile</button>
            <br>
            <br>
            <button class="btn btn-primary d-none" id="readMail" onclick="readMail()">Read Mails</button>
          </div>
        </div>
        </div>
        <br>
        <br>
          <div class="col-md-4">
            <div class="list-group" id="list-tab" role="tablist">
            </div>
          </div>
          <div class="col-md-5">
            <div class="tab-content" id="nav-tabContent">
            </div>
          </div>
        </div>
        <br>
        <br>
    
        <!-- importing bootstrap.js and supporting js libraries -->
        <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
    
        <!-- importing app scripts (load order is important) -->
        <script type="text/javascript" src="./authConfig.js"></script>
        <script type="text/javascript" src="./graphConfig.js"></script>
        <script type="text/javascript" src="./ui.js"></script>
    
        <!-- replace next line with authRedirect.js if you would like to use the redirect flow -->
        <!-- <script type="text/javascript" src="./authRedirect.js"></script>   -->
        <script type="text/javascript" src="./authPopup.js"></script>
        <script type="text/javascript" src="./graph.js"></script>
      </body>
    </html>
    

    提示

    可以将上述脚本中的 MSAL.js 版本替换为 MSAL.js 版本下的最新发布版本。You can replace the version of MSAL.js in the preceding script with the latest released version under MSAL.js releases.

  2. 现在,创建名为 ui.js 的、用于访问和更新 DOM 元素的 .js 文件,并添加以下代码:Now, create a .js file named ui.js, which will access and update DOM elements, and add the following code:

    // Select DOM elements to work with
    const welcomeDiv = document.getElementById("welcomeMessage");
    const signInButton = document.getElementById("signIn");
    const signOutButton = document.getElementById('signOut');
    const cardDiv = document.getElementById("card-div");
    const mailButton = document.getElementById("readMail");
    const profileButton = document.getElementById("seeProfile");
    const profileDiv = document.getElementById("profile-div");
    
    function showWelcomeMessage(account) {
      // Reconfiguring DOM elements
      cardDiv.classList.remove('d-none');
      welcomeDiv.innerHTML = `Welcome ${account.name}`;
      signInButton.classList.add('d-none');
      signOutButton.classList.remove('d-none');
    }
    
    function updateUI(data, endpoint) {
      console.log('Graph API responded at: ' + new Date().toString());
    
      if (endpoint === graphConfig.graphMeEndpoint) {
        const title = document.createElement('p');
        title.innerHTML = "<strong>Title: </strong>" + data.jobTitle;
        const email = document.createElement('p');
        email.innerHTML = "<strong>Mail: </strong>" + data.mail;
        const phone = document.createElement('p');
        phone.innerHTML = "<strong>Phone: </strong>" + data.businessPhones[0];
        const address = document.createElement('p');
        address.innerHTML = "<strong>Location: </strong>" + data.officeLocation;
        profileDiv.appendChild(title);
        profileDiv.appendChild(email);
        profileDiv.appendChild(phone);
        profileDiv.appendChild(address);
    
      } else if (endpoint === graphConfig.graphMailEndpoint) {
          if (data.value.length < 1) {
            alert("Your mailbox is empty!")
          } else {
            const tabList = document.getElementById("list-tab");
            tabList.innerHTML = ''; // clear tabList at each readMail call
            const tabContent = document.getElementById("nav-tabContent");
    
            data.value.map((d, i) => {
              // Keeping it simple
              if (i < 10) {
                const listItem = document.createElement("a");
                listItem.setAttribute("class", "list-group-item list-group-item-action")
                listItem.setAttribute("id", "list" + i + "list")
                listItem.setAttribute("data-toggle", "list")
                listItem.setAttribute("href", "#list" + i)
                listItem.setAttribute("role", "tab")
                listItem.setAttribute("aria-controls", i)
                listItem.innerHTML = d.subject;
                tabList.appendChild(listItem)
    
                const contentItem = document.createElement("div");
                contentItem.setAttribute("class", "tab-pane fade")
                contentItem.setAttribute("id", "list" + i)
                contentItem.setAttribute("role", "tabpanel")
                contentItem.setAttribute("aria-labelledby", "list" + i + "list")
                contentItem.innerHTML = "<strong> from: " + d.from.emailAddress.address + "</strong><br><br>" + d.bodyPreview + "...";
                tabContent.appendChild(contentItem);
              }
            });
          }
      }
    }
    

注册应用程序Register your application

在继续进行身份验证之前,请在 Azure Active Directory 中注册你的应用程序。Before proceeding further with authentication, register your application on Azure Active Directory.

  1. 登录到 Azure 门户Sign in to the Azure portal.
  2. 如果帐户提供访问多个租户的权限,请在右上方选择该帐户,然后将门户会话设置为要使用的 Azure AD 租户。If your account gives you access to more than one tenant, select the account at the upper right, and then set your portal session to the Azure AD tenant that you want to use.
  3. 转到面向开发人员的 Microsoft 标识平台的应用注册页。Go to the Microsoft identity platform for developers App registrations page.
  4. “注册应用程序”页显示后,请输入应用程序的名称。When the Register an application page appears, enter a name for your application.
  5. 在“支持的帐户类型”下,选择“任何组织目录中的帐户”。 Under Supported account types, select Accounts in any organizational directory.
  6. 在“重定向 URI”部分的下拉列表中选择“Web”平台,然后将值设置为基于 Web 服务器的应用程序 URL。 In the Redirect URI section, select the Web platform from the drop-down list, and then set the value to the application URL that's based on your web server.
  7. 选择“注册”。Select Register.
  8. 在应用的“概述”页上,记下“应用程序(客户端) ID”值,供稍后使用 。On the app Overview page, note the Application (client) ID value for later use.
  9. 本快速入门要求启用隐式授权流This quickstart requires the Implicit grant flow to be enabled. 在已注册的应用程序的左窗格中,选择“身份验证”。In the left pane of the registered application, select Authentication.
  10. 在“高级设置”部分的“隐式授权”下,选中“ID 令牌”和“访问令牌”复选框 。In Advanced settings, under Implicit grant, select the ID tokens and Access tokens check boxes. 由于此应用必须将用户登录并调用 API,因此需要 ID 令牌和访问令牌。ID tokens and access tokens are required because this app must sign in users and call an API.
  11. 选择“保存” 。Select Save.

设置 Node.js 的重定向 URLSet a redirect URL for Node.js

对于 Node.js,可在 index.js 文件中设置 Web 服务器端口。For Node.js, you can set the web server port in the index.js file. 本教程使用端口 3000,但你可以使用任何其他可用端口。This tutorial uses port 3000, but you can use any other available port.

若要设置应用程序注册信息中的重定向 URL,请切换回“应用程序注册”窗格,然后执行以下两项操作之一:To set up a redirect URL in the application registration information, switch back to the Application Registration pane, and do either of the following:

  • http://localhost:3000/ 设置为“重定向 URL”。Set http://localhost:3000/ as the Redirect URL.
  • 如果使用的是自定义 TCP 端口,请使用 http://localhost:<port>/(其中,<port> 是自定义 TCP 端口号) 。If you're using a custom TCP port, use http://localhost:<port>/ (where <port> is the custom TCP port number).
    1. 复制“URL”的值。Copy the URL value.
    2. 切换回“应用程序注册”窗格,然后将已复制的值粘贴为“重定向 URL”。Switch back to the Application Registration pane, and paste the copied value as the Redirect URL.

配置 JavaScript SPAConfigure your JavaScript SPA

创建名为 authConfig.js 的、包含用于身份验证的配置参数新 .js 文件,并添加以下代码:Create a new .js file named authConfig.js, which will contain your configuration parameters for authentication, and add the following code:

  const msalConfig = {
    auth: {
      clientId: "Enter_the_Application_Id_Here",
      authority: "Enter_the_Cloud_Instance_Id_HereEnter_the_Tenant_Info_Here",
      redirectUri: "Enter_the_Redirect_Uri_Here",
    },
    cache: {
      cacheLocation: "sessionStorage", // This configures where your cache will be stored
      storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
    }
  };

  // Add here scopes for id token to be used at MS Identity Platform endpoints.
  const loginRequest = {
    scopes: ["openid", "profile", "https://microsoftgraph.chinacloudapi.cn/User.Read"]
  };

  // Add here scopes for access token to be used at MS Graph API endpoints.
  const tokenRequest = {
    scopes: ["Mail.Read"]
  };

其中:Where:

  • <Enter_the_Application_Id_Here> 是所注册应用程序的应用程序(客户端)ID。<Enter_the_Application_Id_Here> is the Application (client) ID for the application you registered.
  • <Enter_the_Cloud_Instance_Id_Here> 是 Azure 云的实例。<Enter_the_Cloud_Instance_Id_Here> is the instance of the Azure cloud. 对于主要或全球 Azure 云,只需输入 https://login.partner.microsoftonline.cnFor the main or global Azure cloud, simply enter https://login.partner.microsoftonline.cn. 对于国家云(例如“中国”云),请参阅国家云For national clouds (for example, China), see National clouds.
  • <Enter_the_Tenant_info_here> 设置为以下选项之一:<Enter_the_Tenant_info_here> is set to one of the following options:
    • 如果应用程序支持“此组织目录中的帐户”,请将此值替换为“租户 ID”或“租户名称”(例如,contoso.microsoft.com)。If your application supports accounts in this organizational directory, replace this value with the Tenant ID or Tenant name (for example, contoso.microsoft.com).
    • 如果应用程序支持“任何组织目录中的帐户”,请将此值替换为 organizationsIf your application supports accounts in any organizational directory, replace this value with organizations.

使用 Microsoft 身份验证库 (MSAL) 登录用户Use the Microsoft Authentication Library (MSAL) to sign in the user

创建名为 authPopup.js 的、包含身份验证和令牌获取逻辑的新 .js 文件,并添加以下代码:Create a new .js file named authPopup.js, which will contain your authentication and token acquisition logic, and add the following code:

const myMSALObj = new Msal.UserAgentApplication(msalConfig);

function signIn() {
  myMSALObj.loginPopup(loginRequest)
    .then(loginResponse => {
      console.log('id_token acquired at: ' + new Date().toString());
      console.log(loginResponse);

      if (myMSALObj.getAccount()) {
        showWelcomeMessage(myMSALObj.getAccount());
      }
    }).catch(error => {
      console.log(error);
    });
}

function signOut() {
  myMSALObj.logout();
}

function callMSGraph(theUrl, accessToken, callback) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function () {
        if (this.readyState == 4 && this.status == 200) {
           callback(JSON.parse(this.responseText));
        }
    }
    xmlHttp.open("GET", theUrl, true); // true for asynchronous
    xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    xmlHttp.send();
}

function getTokenPopup(request) {
  return myMSALObj.acquireTokenSilent(request)
    .catch(error => {
      console.log(error);
      console.log("silent token acquisition fails. acquiring token using popup");

      // fallback to interaction when silent call fails
        return myMSALObj.acquireTokenPopup(request)
          .then(tokenResponse => {
            return tokenResponse;
          }).catch(error => {
            console.log(error);
          });
    });
}

function seeProfile() {
  if (myMSALObj.getAccount()) {
    getTokenPopup(loginRequest)
      .then(response => {
        callMSGraph(graphConfig.graphMeEndpoint, response.accessToken, updateUI);
        profileButton.classList.add('d-none');
        mailButton.classList.remove('d-none');
      }).catch(error => {
        console.log(error);
      });
  }
}

function readMail() {
  if (myMSALObj.getAccount()) {
    getTokenPopup(tokenRequest)
      .then(response => {
        callMSGraph(graphConfig.graphMailEndpoint, response.accessToken, updateUI);
      }).catch(error => {
        console.log(error);
      });
  }
}

详细信息More information

用户首次选择“登录”按钮后,signIn 方法将调用 loginPopup 以将用户登录。After a user selects the Sign In button for the first time, the signIn method calls loginPopup to sign in the user. 此方法会打开一个包含 Microsoft 标识平台终结点的弹出窗口,以提示并验证用户的凭据。This method opens a pop-up window with the Microsoft identity platform endpoint to prompt and validate the user's credentials. 成功登录后,用户将重定向回到原始的 index.html 页。After a successful sign-in, the user is redirected back to the original index.html page. 他们将接收到一个由 msal.js 处理的令牌,该令牌包含的信息已缓存。A token is received, processed by msal.js, and the information contained in the token is cached. 该令牌称为 ID令牌,并包含有关用户的基本信息,如用户显示名。This token is known as the ID token and contains basic information about the user, such as the user display name. 如果计划将此令牌提供的数据用于任何目的,则需确保此令牌已由后端服务器验证,以保证将令牌颁发给应用程序的有效用户。If you plan to use any data provided by this token for any purposes, you need to make sure this token is validated by your backend server to guarantee that the token was issued to a valid user for your application.

本指南生成的 SPA 调用 acquireTokenSilent 和/或 acquireTokenPopup 来获取用于查询 Microsoft Graph API 以获取用户配置文件信息的访问令牌。The SPA generated by this guide calls acquireTokenSilent and/or acquireTokenPopup to acquire an access token used to query the Microsoft Graph API for user profile info. 如果需要用于验证 ID 令牌的示例,请查看 GitHub 中的示例应用程序。If you need a sample that validates the ID token, take a look at this sample application in GitHub. 该示例使用 ASP.NET Web API 进行令牌验证。The sample uses an ASP.NET web API for token validation.

以交互方式获取用户令牌Get a user token interactively

首次登录后,你不希望在每次用户需要请求令牌来访问资源时,都要求他们重新进行身份验证。After the initial sign-in, you do not want to ask users to reauthenticate every time they need to request a token to access a resource. 因此,在大部分时间应使用 acquireTokenSilent 来获取令牌。So acquireTokenSilent should be used most of the time to acquire tokens. 但在某些情况下,需要强制用户与 Microsoft 标识平台终结点交互。There are situations, however, where you need to force users to interact with Microsoft identity platform endpoint. 示例包括:Examples include:

  • 由于密码已过期,用户可能需要重新输入其凭据。Users need to reenter their credentials because the password has expired.
  • 应用程序正在请求访问资源,这需要用户的许可。Your application is requesting access to a resource, and you need the user's consent.
  • 需要双重身份验证。Two-factor authentication is required.

调用 acquireTokenPopup 会打开一个弹出窗口(或者,acquireTokenRedirect 会将用户重定向到 Microsoft 标识平台终结点)。Calling acquireTokenPopup opens a pop-up window (or acquireTokenRedirect redirects users to the Microsoft identity platform endpoint). 在该窗口中,为了进行交互,用户需要确认其凭据、为所需的资源提供许可,或者完成双重身份验证。In that window, users need to interact by confirming their credentials, giving consent to the required resource, or completing the two-factor authentication.

以无提示方式获取用户令牌Get a user token silently

acquireTokenSilent 方法处理令牌获取和续订,无需进行任何用户交互。The acquireTokenSilent method handles token acquisition and renewal without any user interaction. 首次执行 loginPopup(或 loginRedirect)后,通常使用 acquireTokenSilent 方法获取用于访问受保护资源的令牌,以便进行后续调用。After loginPopup (or loginRedirect) is executed for the first time, acquireTokenSilent is the method commonly used to obtain tokens used to access protected resources for subsequent calls. (请求或续订令牌的调用是以静默方式发出的。)acquireTokenSilent 在某些情况下可能会失败。(Calls to request or renew tokens are made silently.) acquireTokenSilent may fail in some cases. 例如,用户的密码可能已过期。For example, the user's password may have expired. 应用程序可以通过两种方式处理此异常:Your application can handle this exception in two ways:

  1. 立即调用 acquireTokenPopup,这会触发用户登录提示。Make a call to acquireTokenPopup immediately, which triggers a user sign-in prompt. 此模式通常用于联机应用程序,此时应用程序中没有可供用户使用的未经身份验证的内容。This pattern is commonly used in online applications where there is no unauthenticated content in the application available to the user. 本指导式设置生成的示例使用此模式。The sample generated by this guided setup uses this pattern.

  2. 应用程序还可以直观地提示用户以交互方式登录,用户可以选择在合适的时间登录,或者应用程序可以稍后重试 acquireTokenSilentApplications can also make a visual indication to the user that an interactive sign-in is required, so the user can select the right time to sign in, or the application can retry acquireTokenSilent at a later time. 如果用户可以在不中断应用程序的情况下使用应用程序的其他功能,则通常会使用此方法。This is commonly used when the user can use other functionality of the application without being disrupted. 例如,应用程序中有可用的未经身份验证的内容。For example, there might be unauthenticated content available in the application. 在这种情况下,用户可以决定何时登录并访问受保护的资源,或何时刷新已过时的信息。In this situation, the user can decide when they want to sign in to access the protected resource, or to refresh the outdated information.

备注

本快速入门默认使用 loginPopupacquireTokenPopup 方法。This quickstart uses the loginPopup and acquireTokenPopup methods by default. 如果使用 Internet Explorer 作为浏览器,我们建议使用 loginRedirectacquireTokenRedirect 方法,因为 Internet Explorer 处理弹出窗口的方式存在一个已知问题If you are using Internet Explorer as your browser, it is recommended to use loginRedirect and acquireTokenRedirect methods, due to a known issue related to the way Internet Explorer handles pop-up windows. 若要了解如何使用 Redirect methods 实现相同的结果,请参阅此文If you would like to see how to achieve the same result using Redirect methods, please see.

使用刚刚获取的令牌调用 Microsoft Graph APICall the Microsoft Graph API by using the token you just acquired

  1. 首先,创建名为 graphConfig.js 的 .js 文件用于存储 REST 终结点。First, create a .js file named graphConfig.js, which will store your REST endpoints. 添加以下代码:Add the following code:

       const graphConfig = {
         graphMeEndpoint: "Enter_the_Graph_Endpoint_Herev1.0/me",
         graphMailEndpoint: "Enter_the_Graph_Endpoint_Herev1.0/me/messages"
       };
    

    其中:Where:

    • <Enter_the_Graph_Endpoint_Here> 是 MS Graph API 的实例。<Enter_the_Graph_Endpoint_Here> is the instance of MS Graph API. 对于全局 MS Graph API 终结点,只需将此字符串替换为 https://microsoftgraph.chinacloudapi.cn 即可。For the global MS Graph API endpoint, simply replace this string with https://microsoftgraph.chinacloudapi.cn. 对于国家云部署,请参阅 Graph API 文档For national cloud deployments, please refer to Graph API Documentation.
  2. 接下来,创建名为 graph.js 的、用于对 Microsoft Graph API 发出 REST 调用的 .js 文件,并添加以下代码:Next, create a .js file named graph.js, which will make a REST call to Microsoft Graph API, and add the following code:

    function callMSGraph(endpoint, token, callback) {
      const headers = new Headers();
      const bearer = `Bearer ${token}`;
    
      headers.append("Authorization", bearer);
    
      const options = {
          method: "GET",
          headers: headers
      };
    
      console.log('request made to Graph API at: ' + new Date().toString());
    
      fetch(endpoint, options)
        .then(response => response.json())
        .then(response => callback(response, endpoint))
        .catch(error => console.log(error))
    }
    

对受保护 API 进行 REST 调用的详细信息More information about making a REST call against a protected API

在本指南创建的示例应用程序中,将使用 callMSGraph() 方法对需要令牌的受保护资源发出 HTTP GET 请求。In the sample application created by this guide, the callMSGraph() method is used to make an HTTP GET request against a protected resource that requires a token. 然后,该请求将内容返回给调用方。The request then returns the content to the caller. 此方法可在 HTTP 授权标头中添加获取的令牌。This method adds the acquired token in the HTTP Authorization header. 本指南创建的示例应用程序中的资源是 Microsoft Graph API me 终结点,它显示用户个人资料信息。For the sample application created by this guide, the resource is the Microsoft Graph API me endpoint, which displays the user's profile information.

测试代码Test your code

  1. 配置服务器侦听基于“index.html”文件位置的 TCP 端口。Configure the server to listen to a TCP port that's based on the location of your index.html file. 对于 Node.js,请通过在命令提示符下从应用程序文件夹运行以下命令,启动 Web 服务器来侦听该端口:For Node.js, start the web server to listen to the port by running the following commands at a command-line prompt from the application folder:

    npm install
    npm start
    
  2. 在浏览器中输入 http://localhost:3000http://localhost:{port} ,其中,port 是 Web 服务器正在侦听的端口。In your browser, enter http://localhost:3000 or http://localhost:{port}, where port is the port that your web server is listening to. 应会显示 index.html 文件的内容和“登录”按钮。You should see the contents of your index.html file and the Sign In button.

测试应用程序Test your application

在浏览器加载 index.html 文件后,选择“登录”。After the browser loads your index.html file, select Sign In. 系统将提示你使用 Microsoft 标识平台终结点进行登录:You're prompted to sign in with the Microsoft identity platform endpoint:

JavaScript SPA 帐户登录窗口

首次登录到应用程序时,系统会提示你授予其访问你的个人资料的权限,并将你登录:The first time that you sign in to your application, you're prompted to grant it access to your profile and sign you in:

“请求的权限”窗口

查看应用程序结果View application results

登录后,你的用户个人资料信息将在显示的 Microsoft Graph API 响应中返回:After you sign in, your user profile information is returned in the Microsoft Graph API response that's displayed:

Microsoft Graph API 调用的结果

有关作用域和委派权限的详细信息More information about scopes and delegated permissions

Microsoft Graph API 需要 user.read 作用域来读取用户的个人资料。The Microsoft Graph API requires the user.read scope to read a user's profile. 默认情况下,在注册门户上注册的每个应用程序中,都会自动添加此范围。By default, this scope is automatically added in every application that's registered on the registration portal. Microsoft Graph 的其他 API 以及后端服务器的自定义 API 可能需要其他作用域。Other APIs for Microsoft Graph, as well as custom APIs for your back-end server, might require additional scopes. 例如,Microsoft Graph API 需要使用 Mail.Read 范围列出用户的邮件。For example, the Microsoft Graph API requires the Mail.Read scope in order to list the user’s mails.

备注

当你增加作用域数量时,可能会提示用户另外进行许可。The user might be prompted for additional consents as you increase the number of scopes.

帮助和支持Help and support

如果需要帮助、需要报告问题,或者需要详细了解支持选项,请参阅面向开发人员的帮助和支持If you need help, want to report an issue, or would like to learn about your support options, see Help and support for developers.