Миграция приложения JavaScript из ADAL.js в MSAL.js

Библиотека проверки подлинности Майкрософт для JavaScript (MSAL.js, также известная msal-browserкак )2.x, — это библиотека проверки подлинности, используемая с приложениями JavaScript на платформа удостоверений Майкрософт. В этой статье описываются изменения, которые необходимо выполнить, чтобы перенести приложение, использующее ADAL.js, для использования MSAL.js 2.x.

Примечание.

Настоятельно рекомендуем использовать MSAL.js 2.x вместо MSAL.js 1.x. Поток предоставления кода проверки подлинности является более безопасным и позволяет одностраничным приложениям обеспечивать удобную работу пользователей несмотря на то, что в таких браузерах, как Safari, среди прочих преимуществ реализованы меры по соблюдению конфиденциальности для блокировки сторонних файлов cookie.

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

  • Необходимо задать для типаURL-адреса ответа платформы / одностраничные приложения на портале регистрации приложений (если у вас есть другие платформы, добавленные в регистрацию приложения, например в Интернете, необходимо убедиться, что URI перенаправления не перекрываются. См. ограничения URI перенаправления)
  • Необходимо предоставить полизаполнения для функций ES6, на которые используется MSAL.js (например, обещания), чтобы запускать приложения в Интернете Обозреватель
  • Перенос приложений Microsoft Entra в конечную точку версии 2, если вы еще не сделали

Установка и импорт MSAL

Существует два способа установки библиотеки MSAL.js 2.x.

Через npm:

npm install @azure/msal-browser

Затем, в зависимости от модульной системы, импортируйте ее, как показано ниже:

import * as msal from "@azure/msal-browser"; // ESM

const msal = require('@azure/msal-browser'); // CommonJS

Через CDN

Загрузите скрипт в раздел заголовка HTML-документа:

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="https://alcdn.msauth.net/browser/2.14.2/js/msal-browser.min.js"></script>
  </head>
</html>

Дополнительные ссылки на CDN и рекомендации по использованию CDN см. в статье Использование CDN.

Инициализация MSAL

В ADAL.js вы создаете экземпляр класса AuthenticationContext, который затем предоставляет методы для выполнения проверки подлинности (login, acquireTokenPopup и т. д.). Этот объект выступает в качестве представления подключения приложения к серверу авторизации или поставщику удостоверений. При инициализации единственным обязательным параметром является clientId:

window.config = {
  clientId: "YOUR_CLIENT_ID"
};

var authContext = new AuthenticationContext(config);

В MSAL.js вы вместо этого создаете экземпляр класса PublicClientApplication. Как и в ADAL.js, конструктор ожидает объект конфигурации, который содержит по крайней мере параметр clientId. Дополнительные сведения см. в статье Инициализация MSAL.js.

const msalConfig = {
  auth: {
      clientId: 'YOUR_CLIENT_ID'
  }
};

const msalInstance = new msal.PublicClientApplication(msalConfig);

В ADAL.js и MSAL.js URI центра по умолчанию используется https://login.microsoftonline.com/common , если он не указан.

Примечание.

Если вы используете https://login.microsoftonline.com/common центр в версии 2.0, пользователи смогут войти с помощью любой организации Microsoft Entra или личной учетной записи Майкрософт (MSA). В MSAL.js, если вы хотите ограничить вход любой учетной записью Microsoft Entra (то же поведение, что и с ADAL.js), используйте https://login.microsoftonline.com/organizations вместо этого.

Настройка MSAL

В MSAL.js объявлены устаревшими некоторые параметры конфигурации из ADAL.js, которые используются при инициализации объекта AuthenticationContext, и добавлены новые. См. полный список доступных параметров. Важно отметить, что многие из этих параметров, за исключением clientId, могут быть переопределены во время получения маркера, что позволяет вам задавать их для каждого запроса. Например, можно использовать URI центра авторизации или URI перенаправления, отличный от того, который задавался во время инициализации при получении маркеров.

Кроме того, в параметрах конфигурации больше не нужно указывать интерфейс входа (т. е. с использованием всплывающих окон или перенаправления страницы). Вместо этого в MSAL.js представлены методы loginPopup и loginRedirect, доступные через экземпляр PublicClientApplication.

Включение ведения журналов

В ADAL.js необходимо отдельно настраивать ведение журнала в любом месте кода:

window.config = {
  clientId: "YOUR_CLIENT_ID"
};

var authContext = new AuthenticationContext(config);

var Logging = {
  level: 3,
  log: function (message) {
      console.log(message);
  },
  piiLoggingEnabled: false
};


authContext.log(Logging)

В MSAL.js ведение журнала задается в параметрах конфигурации и включается во время инициализации PublicClientApplication:

const msalConfig = {
  auth: {
      // authentication related parameters
  },
  cache: {
      // cache related parameters
  },
  system: {
      loggerOptions: {
          loggerCallback(loglevel, message, containsPii) {
              console.log(message);
          },
          piiLoggingEnabled: false,
          logLevel: msal.LogLevel.Verbose,
      }
  }
}

const msalInstance = new msal.PublicClientApplication(msalConfig);

Переход на API MSAL

Для некоторых общедоступных методов в ADAL.js имеются эквиваленты в MSAL.js:

ADAL MSAL Примечания.
acquireToken acquireTokenSilent Переименован и теперь ожидается объект учетной записи
acquireTokenPopup acquireTokenPopup Теперь асинхронный и возвращает обещание
acquireTokenRedirect acquireTokenRedirect Теперь асинхронный и возвращает обещание
handleWindowCallback handleRedirectPromise Требуется при использовании перенаправления
getCachedUser getAllAccounts Переименован и теперь возвращает массив учетных записей.

Другие являются устаревшими, в то время как MSAL.js предлагает новые методы:

ADAL MSAL Примечания.
login Н/П Устарело. Используйте loginPopup или loginRedirect
logOut Н/П Устарело. Используйте logoutPopup или logoutRedirect
Неприменимо loginPopup
Н/Д loginRedirect
Н/Д logoutPopup
Н/Д logoutRedirect
Неприменимо getAccountByHomeId Фильтрует учетные записи по домашнему ИД (ИД объекта (OID) + ИД клиента)
Н/П getAccountLocalId Фильтрует учетные записи по локальному ИД (полезно для ADFS)
Н/П getAccountUsername Фильтрует учетные записи по имени пользователя (если существует)

Кроме того, по мере реализации библиотеки MSAL.js в TypeScript она, в отличие от ADAL.js, предоставляет различные типы и интерфейсы, которые можно использовать в проектах. Дополнительные сведения см. в справочнике по API MSAL.js.

Использование областей вместо ресурсов

Важное различие между конечными точками Azure Active Directory версии 1.0 и 2.0 заключается в том, как к ресурсам обращаются. При использовании ADAL.js с конечной точкой версии 1.0 сначала необходимо зарегистрировать разрешение на портале регистрации приложений, а затем запросить маркер доступа для ресурса (например, Microsoft Graph), как показано ниже:

authContext.acquireTokenRedirect("https://graph.microsoft.com", function (error, token) {
  // do something with the access token
});

MSAL.js поддерживает только конечную точку версии 2.0 . Конечная точка версии 2.0 использует модель доступа к ресурсам на основе областей. Таким образом, при запросе токена доступа для ресурса также необходимо указать область для этого ресурса:

msalInstance.acquireTokenRedirect({
  scopes: ["https://graph.microsoft.com/User.Read"]
});

Одним из преимуществ модели на основе областей является возможность использовать динамические области. При создании приложений с использованием конечной точки версии 1.0 нужно было регистрировать полный набор разрешений (статические области), которые пользователь должен подтверждать во время входа в приложение. В версии 2.0 можно использовать параметр области, который может запросить разрешения в тот момент, когда они нужны (т. е. динамические области). Это позволяет пользователю дополнять список подтвержденных разрешений для областей. Таким образом, в начале работы вы можете позволить пользователю просто войти в приложение без запроса прав доступа, если они пока не нужны. Позже, когда потребуется возможность чтения календаря пользователя, вы укажете область календаря в методе получения маркера и получите согласие пользователя. Дополнительные сведения см. в разделе Ресурсы и области

Использование обещаний вместо обратных вызовов

В ADAL.js обратные вызовы используются для любой операции после успешной проверки подлинности и получения ответа:

authContext.acquireTokenPopup(resource, extraQueryParameter, claims, function (error, token) {
  // do something with the access token
});

В MSAL.js вместо них используются обещания:

msalInstance.acquireTokenPopup({
      scopes: ["User.Read"] // shorthand for https://graph.microsoft.com/User.Read
  }).then((response) => {
      // do something with the auth response
  }).catch((error) => {
      // handle errors
  });

Также можно использовать синтаксис async/await, входящий в состав ES8:

const getAccessToken = async() => {
  try {
      const authResponse = await msalInstance.acquireTokenPopup({
          scopes: ["User.Read"]
      });
  } catch (error) {
      // handle errors
  }
}

Кэш и получение маркеров

Как и ADAL.js, MSAL.js кэширует маркеры и другие артефакты проверки подлинности в хранилище браузера с помощью API веб-хранилища. Рекомендуется использовать sessionStorage параметр (см. конфигурацию), так как он более безопасный при хранении маркеров, приобретенных пользователями, но localStorage даст вам Единый вход на вкладках и сеансах пользователей.

Важно, что вы не должны обращаться к кэшу напрямую. Вместо этого следует использовать соответствующий API MSAL.js для получения артефактов проверки подлинности, таких как маркеры доступа или учетные записи пользователей.

Продление маркеров с помощью маркеров обновления

ADAL.js использует неявный поток OAuth 2.0, который не возвращает маркеры обновления по соображениям безопасности (маркеры обновления имеют больше времени существования, чем маркеры доступа и поэтому более опасны в руках вредоносных субъектов). Поэтому ADAL.js выполняет продление маркера с помощью скрытого IFrame, чтобы пользователь не неоднократно запрашивал проверку подлинности.

С помощью потока кода проверки подлинности с поддержкой PKCE приложения, использующие MSAL.js 2.x, получают маркеры обновления вместе с ИД и маркерами доступа, которые могут использоваться для их продления. Использование маркеров обновления абстрагируется, и разработчики не должны создавать логику вокруг них. Вместо этого MSAL управляет продлением маркеров с помощью маркеров обновления. Предыдущий кэш маркеров с ADAL.js не будет передаваться в MSAL.js, так как схема кэша маркеров изменилась и несовместима с схемой, используемой в ADAL.js.

Обработка ошибок и исключений

При использовании MSAL.js наиболее распространенным типом ошибки может стать interaction_in_progress. Эта ошибка возникает при вызове интерактивного API (loginPopup, loginRedirect, acquireTokenPopup, acquireTokenRedirect), когда еще не завершено выполнение другого интерактивного API. login*acquireToken* Интерфейсы API являются асинхронными, поэтому необходимо убедиться, что полученные обещания разрешаются перед вызовом другого.

Еще одна распространенная ошибка — interaction_required. Эта ошибка часто устраняется путем инициирования запроса на получение интерактивных маркеров. Например, веб-API, к которому вы пытаетесь получить доступ, может иметь политику условного доступа , требуя от пользователя выполнения многофакторной проверки подлинности (MFA). В этом случае обработка ошибки interaction_required путем запуска acquireTokenPopup или acquireTokenRedirect приведет к появлению у пользователя запроса на MFA, позволяя ее пройти.

Еще одна распространенная ошибка, consent_requiredкоторая возникает, когда разрешения, необходимые для получения маркера доступа для защищенного ресурса, не получают согласия пользователя. Как и в случае с interaction_required, для устранения ошибки consent_required зачастую необходимо инициализировать интерактивный запрос на получение маркера с помощью acquireTokenPopup или acquireTokenRedirect.

Дополнительные сведения см. в статье Распространенные ошибки MSAL.js и способы их обработки.

Использование API событий

MSAL.js (версии 2.4 и выше) представляет API событий, который можно использовать в приложениях. Эти события связаны с процессом проверки подлинности и действиями, выполняемыми MSAL в любой момент, и могут использоваться для обновления пользовательского интерфейса, отображения сообщений об ошибках, проверки выполнения какого-либо взаимодействия и т. д. Например, ниже приведен обратный вызов события, который будет вызываться при сбое процесса входа по какой-либо причине.

const callbackId = msalInstance.addEventCallback((message) => {
  // Update UI or interact with EventMessage here
  if (message.eventType === EventType.LOGIN_FAILURE) {
      if (message.error instanceof AuthError) {
          // Do something with the error
      }
    }
});

Для повышения производительности важно отменить регистрацию обратных вызовов событий, когда они больше не нужны. Дополнительные сведения см. в статье API событий MSAL.js.

Обработка нескольких учетных записей

ADAL.js используется концепция пользователя как объекта, который на данный момент прошел проверку подлинности. MSAL.js заменяет пользователейучетными записями, учитывая тот факт, что у пользователя может быть несколько учетных записей. Это также означает, что теперь необходимо управлять несколькими учетными записями и выбирать подходящую для работы. Этот процесс показан в приведенном ниже фрагменте кода.

let homeAccountId = null; // Initialize global accountId (can also be localAccountId or username) used for account lookup later, ideally stored in app state

// This callback is passed into `acquireTokenPopup` and `acquireTokenRedirect` to handle the interactive auth response
function handleResponse(resp) {
  if (resp !== null) {
      homeAccountId = resp.account.homeAccountId; // alternatively: resp.account.homeAccountId or resp.account.username
  } else {
      const currentAccounts = myMSALObj.getAllAccounts();
      if (currentAccounts.length < 1) { // No cached accounts
          return;
      } else if (currentAccounts.length > 1) { // Multiple account scenario
          // Add account selection logic here
      } else if (currentAccounts.length === 1) {
          homeAccountId = currentAccounts[0].homeAccountId; // Single account scenario
      }
  }
}

Дополнительные сведения см. в статье Учетные записи в MSAL.js.

Использование библиотек-оболочек

Если вы разрабатываете платформы Angular и React, можно использовать MSAL Angular версии 2 и MSAL React соответственно. Эти оболочки предоставляют тот же открытый API, что и MSAL.js, а также методы и компоненты для конкретной платформы, которые позволяют оптимизировать процессы проверки подлинности и получения маркеров.

Выполнить приложение

После внесения изменений запустите приложение и протестируйте сценарий проверки подлинности:

npm start

Пример. Защита SPA с помощью ADAL.js и MSAL.js

В приведенных ниже фрагментах показан минимальный код, необходимый для одностраничного приложения, которое выполняет проверку подлинности пользователей с помощью платформы удостоверений Майкрософт и получает маркер доступа для Microsoft Graph сначала с помощью ADAL.js, а затем с помощью MSAL.js.

Использование ADAL.js Использование MSAL.js

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript" src="https://alcdn.msauth.net/lib/1.0.18/js/adal.min.js"></script>
</head>

<body>
    <div>
        <p id="welcomeMessage" style="visibility: hidden;"></p>
        <button id="loginButton">Login</button>
        <button id="logoutButton" style="visibility: hidden;">Logout</button>
        <button id="tokenButton" style="visibility: hidden;">Get Token</button>
    </div>
    <script>
        // DOM elements to work with
        var welcomeMessage = document.getElementById("welcomeMessage");
        var loginButton = document.getElementById("loginButton");
        var logoutButton = document.getElementById("logoutButton");
        var tokenButton = document.getElementById("tokenButton");

        // if user is logged in, update the UI
        function updateUI(user) {
            if (!user) {
                return;
            }

            welcomeMessage.innerHTML = 'Hello ' + user.profile.upn + '!';
            welcomeMessage.style.visibility = "visible";
            logoutButton.style.visibility = "visible";
            tokenButton.style.visibility = "visible";
            loginButton.style.visibility = "hidden";
        };

        // attach logger configuration to window
        window.Logging = {
            piiLoggingEnabled: false,
            level: 3,
            log: function (message) {
                console.log(message);
            }
        };

        // ADAL configuration
        var adalConfig = {
            instance: 'https://login.microsoftonline.com/',
            clientId: "ENTER_CLIENT_ID_HERE",
            tenant: "ENTER_TENANT_ID_HERE",
            redirectUri: "ENTER_REDIRECT_URI_HERE",
            cacheLocation: "sessionStorage",
            popUp: true,
            callback: function (errorDesc, token, error, tokenType) {
                if (error) {
                    console.log(error, errorDesc);
                } else {
                    updateUI(authContext.getCachedUser());
                }
            }
        };

        // instantiate ADAL client object
        var authContext = new AuthenticationContext(adalConfig);

        // handle redirect response or check for cached user
        if (authContext.isCallback(window.location.hash)) {
            authContext.handleWindowCallback();
        } else {
            updateUI(authContext.getCachedUser());
        }

        // attach event handlers to button clicks
        loginButton.addEventListener('click', function () {
            authContext.login();
        });

        logoutButton.addEventListener('click', function () {
            authContext.logOut();
        });

        tokenButton.addEventListener('click', () => {
            authContext.acquireToken(
                "https://graph.microsoft.com",
                function (errorDesc, token, error) {
                    if (error) {
                        console.log(error, errorDesc);

                        authContext.acquireTokenPopup(
                            "https://graph.microsoft.com",
                            null, // extraQueryParameters
                            null, // claims
                            function (errorDesc, token, error) {
                                if (error) {
                                    console.log(error, errorDesc);
                                } else {
                                    console.log(token);
                                }
                            }
                        );
                    } else {
                        console.log(token);
                    }
                }
            );
        });
    </script>
</body>

</html>


<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript" src="https://alcdn.msauth.net/browser/2.34.0/js/msal-browser.min.js"></script>
</head>

<body>
    <div>
        <p id="welcomeMessage" style="visibility: hidden;"></p>
        <button id="loginButton">Login</button>
        <button id="logoutButton" style="visibility: hidden;">Logout</button>
        <button id="tokenButton" style="visibility: hidden;">Get Token</button>
    </div>
    <script>
        // DOM elements to work with
        const welcomeMessage = document.getElementById("welcomeMessage");
        const loginButton = document.getElementById("loginButton");
        const logoutButton = document.getElementById("logoutButton");
        const tokenButton = document.getElementById("tokenButton");

        // if user is logged in, update the UI
        const updateUI = (account) => {
            if (!account) {
                return;
            }

            welcomeMessage.innerHTML = `Hello ${account.username}!`;
            welcomeMessage.style.visibility = "visible";
            logoutButton.style.visibility = "visible";
            tokenButton.style.visibility = "visible";
            loginButton.style.visibility = "hidden";
        };

        // MSAL configuration
        const msalConfig = {
            auth: {
                clientId: "ENTER_CLIENT_ID_HERE",
                authority: "https://login.microsoftonline.com/ENTER_TENANT_ID_HERE",
                redirectUri: "ENTER_REDIRECT_URI_HERE",
            },
            cache: {
                cacheLocation: "sessionStorage"
            },
            system: {
                loggerOptions: {
                    loggerCallback(loglevel, message, containsPii) {
                        console.log(message);
                    },
                    piiLoggingEnabled: false,
                    logLevel: msal.LogLevel.Verbose,
                }
            }
        };

        // instantiate MSAL client object
        const pca = new msal.PublicClientApplication(msalConfig);

        // handle redirect response or check for cached user
        pca.handleRedirectPromise().then((response) => {
            if (response) {
                pca.setActiveAccount(response.account);
                updateUI(response.account);
            } else {
                const account = pca.getAllAccounts()[0];
                updateUI(account);
            }
        }).catch((error) => {
            console.log(error);
        });

        // attach event handlers to button clicks
        loginButton.addEventListener('click', () => {
            pca.loginPopup().then((response) => {
                pca.setActiveAccount(response.account);
                updateUI(response.account);
            })
        });

        logoutButton.addEventListener('click', () => {
            pca.logoutPopup().then((response) => {
                window.location.reload();
            });
        });

        tokenButton.addEventListener('click', () => {
            const account = pca.getActiveAccount();

            pca.acquireTokenSilent({
                account: account,
                scopes: ["User.Read"]
            }).then((response) => {
                console.log(response);
            }).catch((error) => {
                if (error instanceof msal.InteractionRequiredAuthError) {
                    pca.acquireTokenPopup({
                        scopes: ["User.Read"]
                    }).then((response) => {
                        console.log(response);
                    });
                }

                console.log(error);
            });
        });
    </script>
</body>

</html>

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