Руководство. Вход пользователей и вызов API Microsoft Graph из одностраничного приложения JavaScript (SPA) с помощью потока кода проверки подлинности

В этом руководстве показано, как создать одностраничное приложение (SPA) JavaScript, которое поддерживает вход пользователей и вызовы Microsoft Graph с использованием потока кода авторизации с PKCE. Создаваемое одностраничное приложение использует библиотеку проверки подлинности Майкрософт (MSAL) для JavaScript версии 2.0.

В этом руководстве рассматриваются следующие темы:

  • Выполнение потока кода авторизации OAuth 2.0 с PKCE
  • Вход в личные, рабочие и учебные учетные записи Майкрософт
  • Получение маркера доступа
  • Вызов API Microsoft Graph или собственного API, которые требуют маркеры доступа, полученные от платформы удостоверений Майкрософт

MSAL.js 2.0 — это расширенная версия MSAL.js 1.0, поддерживающая поток кода авторизации в браузере вместо потока неявного предоставления разрешения. MSAL.js 2.0 НЕ поддерживает неявный поток.

Необходимые компоненты

  • Node.js для запуска локального веб-сервера
  • Visual Studio Code или любой другой редактор кода.

Принципы работы приложения из учебника

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

Созданное в этом руководстве приложение позволяет SPA JavaScript запрашивать API Microsoft Graph путем получения маркеров безопасности из платформа удостоверений Майкрософт. В этом сценарии после входа в систему маркер доступа запрашивается и добавляется в HTTP-запросы в заголовок авторизации. Получение маркера и его обновление выполняет библиотека проверки подлинности Майкрософт (MSAL.js).

В этом руководстве используется MSAL.js пакет браузера Microsoft Authentication Library для JavaScript версии 2.0.

Получение готового примера кода

Вместо этого можно скачать пример проекта этого руководства, клонируя репозиторий ms-identity-javascript-v2 .

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

Чтобы запустить скачанный проект в локальной среде разработки, создайте сервер localhost для приложения, как описано в шаге 1 статьи в разделе Создание проекта. После этого вы можете настроить пример кода, пропустив шаг настройки.

Чтобы продолжить работу с руководством и самостоятельно создать приложение, перейдите к разделу Создание проекта.

Создание проекта

После установки Node.js создайте папку для размещения приложения, напримерmsal-spa-tutorial.

Теперь реализуйте небольшой веб-сервер Express для обслуживания файла 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}...`);
    

Создание пользовательского интерфейса одностраничного приложения

  1. Создайте в каталоге проекта папку app, а в ней — файл index.html для одностраничного приложения JavaScript. Этот файл используется для реализации пользовательского интерфейса, созданного на платформе Bootstrap 4, и импорта файлов скриптов для настройки, проверки подлинности и вызовов 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. Затем в папке app создайте файл с именем 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

В папке app создайте файл с именем 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
};

В той же папке app создайте файл с именем graphConfig.js. Добавьте следующий код, чтобы предоставить приложению параметры конфигурации для вызова API Microsoft Graph:

// 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 экземпляр API Microsoft Graph, с которым должно взаимодействовать приложение.
    • Для глобальной конечной точки API Microsoft Graph замените оба экземпляра этой строки на https://graph.microsoft.com.
    • Дополнительные сведения о конечных точках в национальных облачных развертываниях см. в статье Национальные облачные развертывания в документации по Microsoft Graph.

Если вы используете глобальную конечную точку, значения graphMeEndpoint и graphMailEndpoint в файле authConfig.js должно выглядеть примерно так:

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

Использование библиотеки проверки подлинности Майкрософт (MSAL) для входа пользователя

Всплывающее окно

В папке app создайте файл с именем 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 в папке app и добавьте следующий код для проверки подлинности и получения маркера для перенаправления входа:

// 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 открывает всплывающее окно с конечной точкой платформы удостоверений Майкрософт, чтобы запросить и проверить учетные данные пользователя. После успешного входа msal.js инициирует поток кода авторизации.

На этом этапе код авторизации, защищенный с помощью PKCE, отправляется в конечную точку маркеров, защищенную с помощью CORS, и обменивается на маркеры. Приложение получает маркер идентификатора, маркер доступа и маркер обновления, msal.js их обрабатывает, а информация из этих маркеров кэшируется.

Маркер идентификатора содержит основные сведения о пользователе, например отображаемое имя. Если вы планируете использовать какие-либо данные, предоставляемые этим маркером идентификации, внутренний сервер должен его проверить и подтвердить, что маркер выдан допустимому пользователю для вашего приложения.

Срок действия маркера доступа ограничен и истекает через 24 часа. Маркер обновления можно использовать для автоматического получения новый маркеров доступа.

Одностраничное приложение, созданное в соответствии с инструкциями в этом учебнике, вызывает acquireTokenSilent и (или) acquireTokenPopup, чтобы получить маркер доступа, используемый при запросе API Microsoft Graph для получения сведений из профиля пользователя. Если вам нужен пример с проверкой маркера идентификатора, ознакомьтесь с примером приложения active-directory-javascript-singlepageapp-dotnet-webapi-v2 на сайте GitHub. В нем для проверки маркеров используется веб-API ASP.NET.

Интерактивное получение маркера

После первоначального входа приложение не должно требовать от пользователей повторно проходить проверку подлинности при каждой попытке доступа к защищенному ресурсу (то есть запрашивать маркер). Чтобы избежать запросов на повторную проверку подлинности, вызовите acquireTokenSilent. Но иногда требуется настроить принудительное взаимодействие пользователей с платформой удостоверений Майкрософт. Например:

  • Пользователям нужно повторно вводить учетные данные, когда истекает срок действия пароля.
  • Приложение запрашивает доступ к ресурсу, на обращение к которому пользователь должен дать согласие.
  • Требуется двухфакторная проверка подлинности.

При вызове acquireTokenPopup открывается всплывающее окно (или acquireTokenRedirect перенаправляет пользователей на платформу удостоверений Майкрософт). В этом окне пользователям необходимо подтвердить учетные данные, предоставить согласие на требуемый ресурс или выполнить двухфакторную проверку подлинности.

Автоматическое получение маркера пользователя

Метод acquireTokenSilent отвечает за получение и обновление маркера без участия пользователя. После первого выполнения метода loginPopup или loginRedirect обычно используется метод acquireTokenSilent, чтобы получить маркеры для доступа к защищенным ресурсам для последующих вызовов. (Вызовы для запроса или обновления маркеров не требуют взаимодействия с пользователем.) В некоторых случаях выполнение acquireTokenSilent может завершаться сбоем. Например, так происходит, когда истекает срок действия пароля пользователя. Приложение может обработать это исключение двумя способами:

  1. Немедленно вызвать acquireTokenPopup, чтобы активировать запрос на вход для пользователя. Этот шаблон обычно используется в интерактивных приложениях, где пользователю недоступно не прошедшее проверку подлинности содержимое. Пример, созданный в ходе пошаговой настройки, использует этот шаблон.
  2. Визуально уведомить пользователя, что требуется интерактивный вход, чтобы пользователь мог выбрать подходящее время для входа или приложение могло повторить метод acquireTokenSilent ​​позднее. Обычно этот способ применим в тех случаях, когда пользователь может использовать другие функции приложения, на которые это не влияет. Например, в приложении есть содержимое, для доступа к аутентификация не требуется. В этой ситуации пользователь может самостоятельно решить, когда выполнять вход для получения доступа к защищенному ресурсу или обновления устаревших данных.

Примечание.

В этом учебнике по умолчанию используются методы loginPopup и acquireTokenPopup. Если вы используете Internet Explorer, рекомендуется использовать методы loginRedirect и acquireTokenRedirect из-за известной проблемы со всплывающими окнами в этом браузере. Пример достижения такого же результата с помощью методов перенаправления см. на странице authRedirect.js на сайте GitHub.

Вызов API Microsoft Graph

Создайте файл с именем graph.js в папке app и добавьте следующий код для отправки вызовов REST в API Microsoft Graph.

/** 
 * 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));
}

В примере приложения, созданном в соответствии с инструкциями в этом учебнике, для отправки HTTP-запроса GET защищенному ресурсу, которому требуется маркер, используется метод callMSGraph(). Затем метод возвращает содержимое вызывающему объекту. Этот метод добавляет полученный маркер в заголовок авторизации HTTP. Для примера приложения, созданного с помощью этого учебника, защищенным ресурсом является конечная точка me из API Microsoft Graph, которая отображает сведения о профиле выполнившего вход пользователя.

Тестирование приложения

Вы завершили создание приложения и теперь готовы запустить веб-сервер Node.js и протестировать функциональность приложения.

  1. Запустите веб-сервер Node.js, выполнив следующие команды в командной строке из корневой папки проекта:

    npm start
    
  2. В браузере перейдите по адресу http://localhost:3000 или http://localhost:<port>, где <port> — это порт, прослушиваемый вашим веб-сервером. Появится содержимое файла index.html с кнопкой Sign In (Войти).

Вход в приложение

После загрузки файла index.html в браузер нажмите кнопку Sign In (Войти). Вам будет предложено войти с помощью платформы удостоверений Майкрософт:

Web browser displaying sign-in dialog

При первом входе в приложение вам будет предложено предоставить ему доступ к профилю, а также выполнить вход:

Content dialog displayed in web browser

Если вы согласились на запрошенные разрешения, веб-приложение отображает имя пользователя, подписав успешное имя входа:

Results of a successful sign-in in the web browser

Вызов API Graph

После входа выберите See Profile (Просмотреть профиль), чтобы просмотреть сведения о профиле пользователя, возвращенные в ответе на вызов API Microsoft Graph.

Profile information from Microsoft Graph displayed in the browser

Дополнительные сведения об областях и делегированных разрешениях

Для чтения профиля пользователя API Microsoft Graph требуется область user.read. По умолчанию этот область автоматически добавляется в каждое приложение, зарегистрированное в Центре администрирования Microsoft Entra. Для других API Microsoft Graph, а также для пользовательских API вашего внутреннего сервера, могут потребоваться дополнительные области. Например, для отображения сообщений электронной почты пользователя API Microsoft Graph требуется область Mail.Read.

При добавлении областей приложение может запросить у пользователей дополнительное согласие на использование таких добавленных областей.

Если API серверной части не требуется область (что не рекомендуется), вы можете использовать clientId в качестве области в вызовах для получения маркеров.

Справка и поддержка

Если вам нужна помощь, если вы хотите сообщить о проблеме или узнать о доступных вариантах поддержки, воспользуйтесь статьей Возможности получения поддержки и справки для разработчиков.

Следующие шаги

  • Дополнительные сведения см. в статье о создании одностраничного приложения React, которое входит в систему пользователей в следующей серии руководств по нескольким частьм.