教學課程:使用驗證碼流程從 JavaScript 單頁應用程式登入使用者並呼叫 Microsoft Graph API

在本教學課程中,您會建置 JavaScript 單頁應用程式 (SPA),以使用授權碼流程搭配 PKCE 來登入使用者並呼叫 Microsoft Graph。 您所建置的 SPA 會使用適用於 JavaScript v2.0 的 Microsoft 驗證連結庫 (MSAL)。

在本教學課程中:

  • 使用 PKCE 執行 OAuth 2.0 授權碼流程
  • 登入個人 Microsoft 帳戶以及公司及學校帳戶
  • 取得存取權杖
  • 呼叫 Microsoft Graph 或您自己的 API,其需要從 Microsoft 身分識別平台 取得的存取令牌

MSAL.js 2.0 可藉由在瀏覽器中支援授權碼流程,而不是隱含授與流程,來改善 MSAL.js 1.0。 MSAL.js 2.0 不支援隱含流程。

必要條件

教學課程應用程式的運作方式

Diagram showing the authorization code flow in a single-page application

您在本教學課程中建立的應用程式可讓 JavaScript SPA 透過從 Microsoft 身分識別平台 取得安全性令牌來查詢 Microsoft Graph API。 在此案例中,在使用者登入之後,會要求存取令牌,並將其新增至授權標頭中的 HTTP 要求。 令牌取得和更新是由適用於 JavaScript 的 Microsoft 驗證連結庫處理(MSAL.js)。

本教學課程使用 MSAL.js,適用於 JavaScript v2.0 的 Microsoft 驗證連結庫瀏覽器套件。

取得已完成的程式代碼範例

您可以複製 ms-identity-javascript-v2 存放庫,改為下載本教學課程已完成的範例專案。

git clone https://github.com/Azure-Samples/ms-identity-javascript-v2.git

若要在本機開發環境中執行下載的專案,請從為應用程式建立localhost伺服器開始,如建立專案的步驟1所述。 完成後,您可以略過設定步驟設定程式碼範例。

若要繼續進行本教學課程並自行建置應用程式,請繼續進行下一節建立 您的專案

建立專案

安裝Node.js之後,請建立資料夾來載入您的應用程式,例如 msal-spa-tutorial

接下來,實作小型 Express Web 伺服器來提供您的 index.html 檔案。

  1. 首先,變更至終端機中的專案目錄,然後執行下列 npm 命令:

    npm init -y
    npm install @azure/msal-browser
    npm install express
    npm install morgan
    npm install yargs
    
  2. 接下來,建立名為 server.js 的 檔案,並新增下列程序代碼:

    const express = require('express');
    const morgan = require('morgan');
    const path = require('path');
    
    const DEFAULT_PORT = process.env.PORT || 3000;
    
    // initialize express.
    const app = express();
    
    // Initialize variables.
    let port = DEFAULT_PORT;
    
    // Configure morgan module to log all requests.
    app.use(morgan('dev'));
    
    // Setup app folders.
    app.use(express.static('app'));
    
    // Set up a route for index.html
    app.get('*', (req, res) => {
        res.sendFile(path.join(__dirname + '/index.html'));
    });
    
    // Start the server.
    app.listen(port);
    console.log(`Listening on port ${port}...`);
    

建立 SPA UI

  1. 在項目目錄中建立 應用程式 資料夾,並在其中建立 JavaScript SPA 的 index.html檔案。 此檔案會實作使用 Bootstrap 4 Framework 建置的 UI,並匯入腳本檔案以進行組態、驗證和 API 呼叫。

    index.html 檔案中,新增下列程序代碼:

    <!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>Microsoft identity platform</title>
      <link rel="SHORTCUT ICON" href="./favicon.svg" type="image/x-icon">
    
       <!-- msal.min.js can be used in the place of msal.js; included msal.js to make debug easy -->
      <script src="https://alcdn.msauth.net/browser/2.30.0/js/msal-browser.js"
        integrity="sha384-o4ufwq3oKqc7IoCcR08YtZXmgOljhTggRwxP2CLbSqeXGtitAxwYaUln/05nJjit"
        crossorigin="anonymous"></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">
      <link rel="SHORTCUT ICON" href="https://c.s-microsoft.com/favicon.ico?v2" type="image/x-icon">
    </head>
    
    <body>
      <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <a class="navbar-brand" href="/">Microsoft identity platform</a>
        <div class="btn-group ml-auto dropleft">
          <button type="button" id="SignIn" class="btn btn-secondary" onclick="signIn()">
            Sign In
          </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" style="display: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" 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>
    
      <!-- <script type="text/javascript" src="./authRedirect.js"></script>   -->
      <!-- uncomment the above line and comment the line below if you would like to use the redirect flow -->
      <script type="text/javascript" src="./authPopup.js"></script>
      <script type="text/javascript" src="./graph.js"></script>
    </body>
    
    </html>
    
  2. 接下來,在 應用程式 資料夾中,建立名為 ui.js 的檔案,並新增下列程序代碼。 此檔案將會存取及更新 DOM 元素。

    // Select DOM elements to work with
    const welcomeDiv = document.getElementById("WelcomeMessage");
    const signInButton = document.getElementById("SignIn");
    const cardDiv = document.getElementById("card-div");
    const mailButton = document.getElementById("readMail");
    const profileButton = document.getElementById("seeProfile");
    const profileDiv = document.getElementById("profile-div");
    
    function showWelcomeMessage(username) {
        // Reconfiguring DOM elements
        cardDiv.style.display = 'initial';
        welcomeDiv.innerHTML = `Welcome ${username}`;
        signInButton.setAttribute("onclick", "signOut();");
        signInButton.setAttribute('class', "btn btn-success")
        signInButton.innerHTML = "Sign Out";
    }
    
    function updateUI(data, endpoint) {
        console.log('Graph API responded at: ' + new Date().toString());
    
        if (endpoint === graphConfig.graphMeEndpoint) {
            profileDiv.innerHTML = ''
            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) {
                alert("You do not have a mailbox!")
            } else if (data.value.length < 1) {
                alert("Your mailbox is empty!")
            } else {
                const tabContent = document.getElementById("nav-tabContent");
                const tabList = document.getElementById("list-tab");
                tabList.innerHTML = ''; // clear tabList at each readMail call
    
                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);
                    }
                });
            }
        }
    }
    

註冊您的應用程式

請遵循單頁應用程式:應用程式註冊中的步驟,為您的 SPA 建立應用程式註冊。

在 [ 重新導向 URI:MSAL.js 2.0 搭配驗證碼流程 步驟時,輸入 http://localhost:3000,這是本教學課程應用程式執行的預設位置。

如果您想要使用不同的埠,請輸入 http://localhost:<port>,其中 <port> 是您慣用的 TCP 通訊埠號碼。 如果您指定 以外的 3000埠號碼,也請使用您慣用的埠號碼更新 server.js

設定 JavaScript SPA

應用程式資料夾中建立名為 authConfig.js 的檔案,以包含用於驗證的組態參數,然後新增下列程式代碼:

/**
 * Configuration object to be passed to MSAL instance on creation. 
 * For a full list of MSAL.js configuration parameters, visit:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md 
 */
const msalConfig = {
    auth: {
        // 'Application (client) ID' of app registration in Azure portal - this value is a GUID
        clientId: "Enter_the_Application_Id_Here",
        // Full directory URL, in the form of https://login.microsoftonline.com/<tenant-id>
        authority: "Enter_the_Cloud_Instance_Id_HereEnter_the_Tenant_Info_Here",
        // Full redirect URL, in form of http://localhost:3000
        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
    },
    system: {	
        loggerOptions: {	
            loggerCallback: (level, message, containsPii) => {	
                if (containsPii) {		
                    return;		
                }		
                switch (level) {		
                    case msal.LogLevel.Error:		
                        console.error(message);		
                        return;		
                    case msal.LogLevel.Info:		
                        console.info(message);		
                        return;		
                    case msal.LogLevel.Verbose:		
                        console.debug(message);		
                        return;		
                    case msal.LogLevel.Warning:		
                        console.warn(message);		
                        return;		
                }	
            }	
        }	
    }
};

/**
 * Scopes you add here will be prompted for user consent during sign-in.
 * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
 * For more information about OIDC scopes, visit: 
 * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
 */
const loginRequest = {
    scopes: ["User.Read"]
};

/**
 * Add here the scopes to request when obtaining an access token for MS Graph API. For more information, see:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
 */
const tokenRequest = {
    scopes: ["User.Read", "Mail.Read"],
    forceRefresh: false // Set this to "true" to skip a cached token and go to the server to get a new token
};

仍在應用程式資料夾中,建立名為 graphConfig.js的檔案。 新增下列程式代碼,為您的應用程式提供呼叫 Microsoft Graph API 的組態參數:

// Add here the endpoints for MS Graph API services you would like to use.
const graphConfig = {
    graphMeEndpoint: "Enter_the_Graph_Endpoint_Herev1.0/me",
    graphMailEndpoint: "Enter_the_Graph_Endpoint_Herev1.0/me/messages"
};

修改區段中的值 graphConfig ,如這裡所述:

  • Enter_the_Graph_Endpoint_Here 是應用程式應該與其通訊的 Microsoft Graph API 實例。

graphMeEndpoint如果您使用全域端點,graphConfig.js中的graphMailEndpoint 值應該類似下列內容:

graphMeEndpoint: "https://graph.microsoft.com/v1.0/me",
graphMailEndpoint: "https://graph.microsoft.com/v1.0/me/messages"

使用 Microsoft 驗證連結庫 (MSAL) 登入使用者

快顯

應用程式 資料夾中,建立名為 authPopup.js 的檔案,並針對登入彈出視窗新增下列驗證和令牌取得程序代碼:

// Create the main myMSALObj instance
// configuration parameters are located at authConfig.js
const myMSALObj = new msal.PublicClientApplication(msalConfig);

let username = "";

function selectAccount() {

    /**
     * See here for more info on account retrieval: 
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
     */

    const currentAccounts = myMSALObj.getAllAccounts();
    if (currentAccounts.length === 0) {
        return;
    } else if (currentAccounts.length > 1) {
        // Add choose account code here
        console.warn("Multiple accounts detected.");
    } else if (currentAccounts.length === 1) {
        username = currentAccounts[0].username;
        showWelcomeMessage(username);
    }
}

function handleResponse(response) {

    /**
     * To see the full list of response object properties, visit:
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#response
     */

    if (response !== null) {
        username = response.account.username;
        showWelcomeMessage(username);
    } else {
        selectAccount();
    }
}

function signIn() {

    /**
     * You can pass a custom request object below. This will override the initial configuration. For more information, visit:
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#request
     */

    myMSALObj.loginPopup(loginRequest)
        .then(handleResponse)
        .catch(error => {
            console.error(error);
        });
}

function signOut() {

    /**
     * You can pass a custom request object below. This will override the initial configuration. For more information, visit:
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#request
     */

    const logoutRequest = {
        account: myMSALObj.getAccountByUsername(username),
        postLogoutRedirectUri: msalConfig.auth.redirectUri,
        mainWindowRedirectUri: msalConfig.auth.redirectUri
    };

    myMSALObj.logoutPopup(logoutRequest);
}

function getTokenPopup(request) {

    /**
     * See here for more info on account retrieval: 
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
     */
    request.account = myMSALObj.getAccountByUsername(username);
    
    return myMSALObj.acquireTokenSilent(request)
        .catch(error => {
            console.warn("silent token acquisition fails. acquiring token using popup");
            if (error instanceof msal.InteractionRequiredAuthError) {
                // fallback to interaction when silent call fails
                return myMSALObj.acquireTokenPopup(request)
                    .then(tokenResponse => {
                        console.log(tokenResponse);
                        return tokenResponse;
                    }).catch(error => {
                        console.error(error);
                    });
            } else {
                console.warn(error);   
            }
    });
}

function seeProfile() {
    getTokenPopup(loginRequest)
        .then(response => {
            callMSGraph(graphConfig.graphMeEndpoint, response.accessToken, updateUI);
        }).catch(error => {
            console.error(error);
        });
}

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

selectAccount();

重新導向

在應用程式資料夾中建立名為 authRedirect.js檔案,並新增下列驗證和令牌擷取程式代碼以進行登入重新導向:

// Create the main myMSALObj instance
// configuration parameters are located at authConfig.js
const myMSALObj = new msal.PublicClientApplication(msalConfig);

let username = "";

/**
 * A promise handler needs to be registered for handling the
 * response returned from redirect flow. For more information, visit:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/acquire-token.md
 */
myMSALObj.handleRedirectPromise()
    .then(handleResponse)
    .catch((error) => {
        console.error(error);
    });

function selectAccount () {

    /**
     * See here for more info on account retrieval: 
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
     */

    const currentAccounts = myMSALObj.getAllAccounts();

    if (currentAccounts.length === 0) {
        return;
    } else if (currentAccounts.length > 1) {
        // Add your account choosing logic here
        console.warn("Multiple accounts detected.");
    } else if (currentAccounts.length === 1) {
        username = currentAccounts[0].username;
        showWelcomeMessage(username);
    }
}

function handleResponse(response) {
    if (response !== null) {
        username = response.account.username;
        showWelcomeMessage(username);
    } else {
        selectAccount();
    }
}

function signIn() {

    /**
     * You can pass a custom request object below. This will override the initial configuration. For more information, visit:
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#request
     */

    myMSALObj.loginRedirect(loginRequest);
}

function signOut() {

    /**
     * You can pass a custom request object below. This will override the initial configuration. For more information, visit:
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#request
     */

    const logoutRequest = {
        account: myMSALObj.getAccountByUsername(username),
        postLogoutRedirectUri: msalConfig.auth.redirectUri,
    };

    myMSALObj.logoutRedirect(logoutRequest);
}

function getTokenRedirect(request) {
    /**
     * See here for more info on account retrieval: 
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
     */
    request.account = myMSALObj.getAccountByUsername(username);

    return myMSALObj.acquireTokenSilent(request)
        .catch(error => {
            console.warn("silent token acquisition fails. acquiring token using redirect");
            if (error instanceof msal.InteractionRequiredAuthError) {
                // fallback to interaction when silent call fails
                return myMSALObj.acquireTokenRedirect(request);
            } else {
                console.warn(error);   
            }
        });
}

function seeProfile() {
    getTokenRedirect(loginRequest)
        .then(response => {
            callMSGraph(graphConfig.graphMeEndpoint, response.accessToken, updateUI);
        }).catch(error => {
            console.error(error);
        });
}

function readMail() {
    getTokenRedirect(tokenRequest)
        .then(response => {
            callMSGraph(graphConfig.graphMailEndpoint, response.accessToken, updateUI);
        }).catch(error => {
            console.error(error);
        });
}

程式代碼的運作方式

當使用者第一次選取 [登入 ] 按鈕時, signIn 方法會呼叫 loginPopup 以登入使用者。 方法loginPopup會開啟具有 Microsoft 身分識別平台 端點的彈出視窗,以提示並驗證用戶的認證。 成功登入之後,msal.js起始授權碼流程

此時,PKCE 保護的授權碼會傳送至 CORS 保護的令牌端點,並交換令牌。 您的應用程式會收到標識碼令牌、存取令牌和重新整理令牌,並由msal.js處理,且令牌中包含的資訊會快取。

標識元令牌包含使用者的基本資訊,例如其顯示名稱。 如果您打算使用標識碼令牌所提供的任何數據,後端伺服器 必須 驗證它,以確保令牌已發行給應用程式的有效使用者。

存取令牌的存留期有限,並在24小時後到期。 重新整理令牌可用來以無訊息方式取得新的存取令牌。

您在本教學課程中建立的 SPA 會呼叫 acquireTokenSilent 和/或 acquireTokenPopup ,以取得 用來查詢 Microsoft Graph API 的使用者設定檔資訊存取令牌 。 如果您需要驗證標識符令牌的範例,請參閱 GitHub 上的 active-directory-javascript-singlepageapp-dotnet-webapi-v2 範例應用程式。 此範例會使用 ASP.NET Web API 進行令牌驗證。

以互動方式取得使用者令牌

初始登入之後,您的應用程式不應該要求使用者每次需要存取受保護的資源時重新驗證(也就是要求令牌)。 若要防止這類重新驗證要求,請呼叫 acquireTokenSilent。 但是,在某些情况下,您可能需要強制使用者與 Microsoft 身分識別平台進行互動。 例如:

  • 用戶必須重新輸入其認證,因為密碼已過期。
  • 您的應用程式要求存取資源,而且您需要使用者的同意。
  • 需要雙因素驗證。

呼叫acquireTokenPopup會開啟彈出視窗(或acquireTokenRedirect將使用者重新導向至 Microsoft 身分識別平台)。 在該視窗中,用戶必須藉由確認其認證、同意所需的資源,或完成雙因素驗證來進行互動。

以無訊息方式取得使用者令牌

方法會 acquireTokenSilent 處理令牌擷取和更新,而不需要任何用戶互動。 第一次執行 (或loginRedirect) 之後loginPopupacquireTokenSilent是常用來取得令牌的方法,用來存取受保護資源的後續呼叫。 (要求或更新令牌的呼叫會以無訊息方式進行。 acquireTokenSilent 在某些情況下可能會失敗。 例如,用戶的密碼可能已過期。 您的應用程式可以透過兩種方式來處理此例外狀況:

  1. 立即呼叫 acquireTokenPopup 以觸發使用者登入提示。 此模式通常用於在線應用程式,其中應用程式中沒有未經驗證的內容可供使用者使用。 此引導式設定所產生的範例會使用此模式。
  2. 以可視化方式指示使用者需要互動式登入,讓用戶能夠選取正確的登入時間,或應用程式可以在稍後重試 acquireTokenSilent 。 當使用者可以使用應用程式的其他功能而不中斷時,通常會使用這項技術。 例如,應用程式中可能有未經驗證的內容。 在此情況下,用戶可以決定何時要登入以存取受保護的資源,或重新整理過期的資訊。

注意

本教學課程預設會使用 loginPopupacquireTokenPopup 方法。 如果您使用 Internet Explorer,建議您使用 loginRedirectacquireTokenRedirect 方法,因為 Internet Explorer 和彈出視窗有已知問題 。 如需使用重新導向方法達到相同結果的範例,請參閱 GitHub 上的authRedirect.js

呼叫 Microsoft Graph API

在應用程式資料夾中建立名為 graph.js檔案,並新增下列程式代碼以對 Microsoft Graph API 進行 REST 呼叫:

/** 
 * Helper function to call MS Graph API endpoint
 * using the authorization bearer token scheme
*/
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));
}

在本教學課程中建立的範例應用程式中,方法 callMSGraph() 可用來對需要令牌的受保護資源提出 HTTP GET 要求。 然後,要求會將內容傳回給呼叫端。 這個方法會在 HTTP 授權標頭中新增取得的令牌。 在本教學課程中建立的範例應用程式中,受保護的資源是 Microsoft Graph API me 端點,其會顯示已登入使用者的配置檔資訊。

測試您的應用程式

您已完成應用程式的建立,現在已準備好啟動Node.js Web 伺服器並測試應用程式的功能。

  1. 從項目資料夾的根目錄中執行下列命令,以啟動Node.js網頁伺服器:

    npm start
    
  2. 在您的瀏覽器中,流覽至 http://localhost:3000http://localhost:<port>,其中 <port> 是網頁伺服器正在接聽的埠。 您應該會看到index.html檔案的內容和 [登入] 按鈕。

登入應用程式

在瀏覽器載入您的 index.html 檔案之後,選取 [ 登入]。 系統會提示您使用 Microsoft 身分識別平台 登入:

Web browser displaying sign-in dialog

第一次登入應用程式時,系統會提示您將配置檔的存取權授與它,並登入:

Content dialog displayed in web browser

如果您同意要求的許可權,Web 應用程式會顯示您的使用者名稱,表示登入成功:

Results of a successful sign-in in the web browser

呼叫圖形 API

登入之後,選取 [查看配置檔 ] 以檢視從 Microsoft Graph API 呼叫回應中傳回的使用者配置檔資訊:

Profile information from Microsoft Graph displayed in the browser

範圍和委派許可權的詳細資訊

Microsoft Graph API 需要 user.read 範圍才能讀取使用者配置檔。 根據預設,此範圍會自動新增至 Microsoft Entra 系統管理中心註冊的每個應用程式中。 Microsoft Graph 的其他 API,以及後端伺服器的自定義 API,可能需要其他範圍。 例如,Microsoft Graph API 需要 Mail.Read 範圍,才能列出使用者的電子郵件。

當您新增範圍時,系統可能會提示使用者為新增的範圍提供額外的同意。

如果後端 API 不需要範圍,但不建議這麼做,您可以使用 clientId 作為呼叫中取得令牌的範圍。

說明與支援 

如果您需要協助、想要回報問題,或想要了解支援選項,請參閱 開發人員的說明和支援。

下一步

  • 透過建置 React 單頁應用程式 (SPA) 以在下列多部分 教學課程系列中登入使用者,以深入瞭解。