共用方式為


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

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

注意

強烈建議您使用 MSAL.js 1.x 而非 MSAL.js 2.x。 驗證碼授與流程較安全,可讓單一頁面的應用程式維持良好的使用者體驗,儘管像是 Safari 這類瀏覽器已實作為封鎖協力廠商 Cookie,還是有其他優點。

必要條件

  • 您必須將平台 / 回覆 URL 類型設定為應用程式註冊入口網站上的單一頁面應用程式 (如果您在應用程式註冊中新增其他平台 (例如 Web)),則需要確定重新導向 URI 不會重迭。請參閱:重新導向 URI 限制)
  • 您必須針對 MSAL.js 依賴的 ES6 功能提供 polyfill (例如,promise),以便在 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 類別具現化,然後公開可用來達成驗證 (loginacquireTokenPopup 等) 的方法。 此物件可作為應用程式與授權伺服器或身分識別提供者的連線標記法。 初始化時,唯一的必要參數是 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,而不是您在取得權杖時於初始化期間所設定的 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 現在非同步並傳回 Promise
acquireTokenRedirect acquireTokenRedirect 現在非同步並傳回 Promise
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 依使用者名稱篩選帳戶 (若存在)

此外,當 MSAL.js 在 TypeScript 中執行時,與 ADAL.js 不同,其會公開各種類型和介面,讓您可以在專案中使用。 如需詳細資訊,請參閱 MSAL.js API 參考

使用範圍,而非資源

Azure Active Directory v1.0 與 2.0 端點之間的重要差異在於資源的存取方式。 使用 ADAL.js 搭配 v1.0 端點時,您會先在應用程式註冊入口網站上註冊權限,然後為資源 (例如 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 中,您可以使用範圍參數,在您需要時要求權限 (因此,這就是「動態範圍」)。 這可讓使用者對範圍提供增量同意。 因此,如果一開始您只是希望使用者登入您的應用程式,而且您不需要任何一種存取,就可以這麼做。 之後,如果您需要能夠讀取使用者的行事曆,您可以在取得權杖方法中要求行事曆範圍,並取得使用者的同意。 如需詳細資訊,請參閱:資源和範圍

使用承諾而非回呼

在 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 (loginPopuploginRedirectacquireTokenPopup),acquireTokenRedirect 且擲回此錯誤。 login*acquireToken* API 都是非同步,因此您必須先確定所產生的 Promise 已解決,然後再叫用另一個。

另一個常見的錯誤是 interaction_required。 此錯誤通常可透過起始互動式權杖取得提示獲得解決。 例如,您嘗試存取的 Web API 可能會有適當的條件式存取原則,要求使用者執行多重要素驗證 (MFA)。 在這類情況下,藉由觸發 acquireTokenPopupacquireTokenRedirect 來處理 interaction_required 錯誤會提示使用者提供 MFA,允許使用者加以履行。

還有另一個常見的錯誤,就是 consent_required,當使用者未同意取得受保護資源的存取權杖所需的權限時,就會發生這種錯誤。 如同在 interaction_required 中一樣,consent_required 錯誤的解決方案通常會使用 acquireTokenPopupacquireTokenRedirect 方法,來起始互動式權杖取得提示。

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

使用事件 API

MSAL.js (> = v4.0) 引進了事件 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>

下一步