分享方式:


如何將 JavaScript 應用程式從 ADAL.js 遷移至MSAL.js

Microsoft適用於 JavaScript 的驗證連結庫 (MSAL.js也稱為 msal-browser) 2.x 是建議在 Microsoft 身分識別平台 上搭配 JavaScript 應用程式使用的驗證連結庫。 本文強調您需要進行的變更,以移轉使用 ADAL.js 的應用程式,以使用 MSAL.js 2.x

注意

強烈建議MSAL.js 2.x MSAL.js 1.x。 驗證碼授與流程更安全,並允許單頁應用程式維持良好的用戶體驗,儘管Safari等隱私權措施瀏覽器已實作來封鎖第三方Cookie等優點。

必要條件

  • 您必須在應用程式註冊入口網站上將 [平臺 / 回復 URL 類型] 設定為 [單頁應用程式] (如果您已在應用程式註冊中新增其他平臺,例如 Web,您必須確定重新導向 URI 不會重疊。請參閱:重新導向 URI 限制
  • 您必須為MSAL.js依賴的ES6功能提供 polyfills ,才能在Internet Explorer 上 執行您的應用程式
  • 如果您尚未將Microsoft Entra 應用程式遷移至 v2 端點

安裝和匯入 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 為 。

注意

如果您在 v2.0 中使用 https://login.microsoftonline.com/common 授權單位,將允許使用者使用任何 Microsoft Entra 組織或個人Microsoft帳戶 (MSA) 登入。 在MSAL.js中,如果您想要限制登入任何Microsoft Entra 帳戶(與 ADAL.js 的行為相同),請改用 https://login.microsoftonline.com/organizations

設定 MSAL

ADAL.js中某些組態選項在初始化 AuthenticationContext 時所使用的設定選項在 MSAL.js 中已被取代,同時引進一些新選項。 請參閱可用選項的完整清單。 重要的是,除了 clientId之外,這些選項中的許多都可以在令牌取得期間覆寫,讓您根據每個要求設定這些 選項 。 例如,您可以在取得令牌時,使用不同於您在初始化期間設定的授權單位 URI 或重新導向 URI

此外,您不再需要透過組態選項指定登入體驗(也就是使用彈出視窗或重新導向頁面)。 MSAL.js而是透過 PublicClientApplication 實例公開 loginPopuploginRedirect 方法。

啟用 記錄

在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);

切換至 MSAL API

ADAL.js中的某些公用方法在 MSAL.js中具有對等專案:

ADAL MSAL (部分機器翻譯) 備註
acquireToken acquireTokenSilent 已重新命名,現在需要 帳戶 物件
acquireTokenPopup acquireTokenPopup 現在異步並傳回承諾
acquireTokenRedirect acquireTokenRedirect 現在異步並傳回承諾
handleWindowCallback handleRedirectPromise 使用重新導向體驗時需要
getCachedUser getAllAccounts 已重新命名,現在會傳回帳戶陣列。

其他方法已被取代,而MSAL.js提供新的方法:

ADAL MSAL (部分機器翻譯) 備註
login N/A 已取代。 使用 loginPopuploginRedirect
logOut N/A 已取代。 使用 logoutPopuplogoutRedirect
N/A loginPopup
N/A loginRedirect
N/A logoutPopup
N/A logoutRedirect
N/A getAccountByHomeId 依家庭識別碼篩選帳戶 (oid + 租使用者識別碼)
N/A getAccountLocalId 依本機識別碼篩選帳戶(適用於ADFS)
N/A getAccountUsername 依使用者名稱篩選帳戶(如果有)

此外,由於 typeScript 中實作MSAL.js與ADAL.js不同,它會公開您可以在專案中使用的各種類型和介面。 如需詳細資訊, 請參閱MSAL.js API 參考

使用範圍而非資源

Azure Active Directory v1.0 與 2.0 端點之間的重要差異在於如何存取資源。 搭配 v1.0 端點使用ADAL.js時,您會先在應用程式註冊入口網站上註冊許可權,然後要求資源的存取令牌(例如 Microsoft Graph),如下所示:

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

MSAL.js僅 支援 v2.0 端點。 v2.0 端點會採用以範圍為中心的模型來存取資源。 因此,當您要求資源的存取令牌時,您也需要指定該資源的範圍:

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

以範圍為中心的模型的優點之一是能夠使用 動態範圍。 使用 v1.0 端點建置應用程式時,您需要註冊應用程式所需的完整許可權集(稱為 靜態範圍),讓使用者在登入時同意。 在 v2.0 中,您可以使用 scope 參數在您想要的許可權時要求許可權(因此, 動態範圍)。 這可讓使用者對範圍提供 累加同意 。 因此,如果一開始您只想讓使用者登入您的應用程式,而且您不需要任何類型的存取權,您可以這麼做。 如果您稍後需要能夠讀取使用者的行事曆,您可以接著在 acquireToken 方法中要求行事曆範圍,並取得使用者的同意。 如需詳細資訊,請參閱: 資源和範圍

使用承諾而不是回呼

在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
  });

您也可以使用 ES8 隨附的 async/await 語法:

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

快取和擷取令牌

如同ADAL.js,MSAL.js使用 Web 記憶體 API 快取瀏覽器記憶體中的令牌和其他驗證成品。 建議您使用sessionStorage選項(請參閱:組態),因為它在儲存使用者取得的令牌時更安全,但localStorage會讓您跨索引卷標和用戶會話 單一登入

重要的是,您不應該直接存取快取。 相反地,您應該使用適當的MSAL.js API 來擷取驗證成品,例如存取令牌或用戶帳戶。

使用重新整理令牌更新令牌

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 仍在進行中時叫用互動式 API 時loginPopuploginRedirectacquireTokenPopupacquireTokenRedirect就會擲回此錯誤。 和 acquireToken* API 是異步login*,因此您必須確保產生的承諾已在叫用另一個承諾之前解決。

另一個常見的錯誤是 interaction_required。 這個錯誤通常是藉由起始互動式令牌取得提示來解決。 例如,您嘗試存取的 Web API 可能會有條件式存取原則,要求使用者執行多重要素驗證 (MFA)。 在此情況下,藉由觸發 acquireTokenPopupacquireTokenRedirect 會提示使用者輸入 MFA 來處理interaction_required錯誤,讓他們能夠完整處理它。

然而,您可能會遇到的另一個常見錯誤是 consent_required,當使用者未同意取得受保護資源存取令牌所需的許可權時,就會發生這種情況。 如同 在 中 interaction_required,錯誤的解決方案 consent_required 通常會使用 acquireTokenPopupacquireTokenRedirect來起始互動式令牌取得提示。

如需詳細資訊,請參閱: 常見的MSAL.js錯誤及其處理方式

使用事件 API

MSAL.js (>=v2.4) 引進可在應用程式中使用的事件 API。 這些事件與驗證程式以及 MSAL 在任何時刻執行的動作相關,而且可用來更新 UI、顯示錯誤訊息、檢查是否有任何互動正在進行等等。 例如,以下是在登入進程因任何原因而失敗時所呼叫的事件回呼:

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

為了達到效能,當不再需要事件回呼時,請務必取消註冊事件回呼。 如需詳細資訊,請參閱: MSAL.js事件 API

處理多個帳戶

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 v2MSAL React。 這些包裝函式會公開與MSAL.js相同的公用 API,同時提供可簡化驗證和令牌擷取程式的架構特定方法和元件。

執行應用程式

變更完成後,請執行應用程式並測試您的驗證案例:

npm start

範例:使用 ADAL.js 保護 SPA 與 MSAL.js

下列代碼段示範單頁應用程式使用 Microsoft 身分識別平台 驗證使用者所需的最少程式碼,並使用第一個ADAL.js取得 Microsoft Graph 的存取令牌,然後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>

下一步