Jak migrovat javascriptovou aplikaci z ADAL.js na MSAL.js

Knihovna Microsoft Authentication Library pro JavaScript (MSAL.js, označovaná také jako msal-browser) 2.x je ověřovací knihovna, která se doporučuje používat s aplikacemi JavaScriptu na platformě Microsoft Identity Platform. Tento článek popisuje změny, které je potřeba provést při migraci aplikace, která používá ADAL.js k používání MSAL.js 2.x.

Poznámka:

Důrazně doporučujeme MSAL.js 2.x přes MSAL.js 1.x. Tok udělení ověřovacího kódu je bezpečnější a umožňuje jednostrákovým aplikacím udržovat dobré uživatelské prostředí navzdory zásadám ochrany osobních údajů v prohlížečích, jako je Safari implementované, aby mimo jiné blokovaly soubory cookie třetích stran.

Požadavky

  • Typ adresy URL odpovědi platformy / musíte nastavit na jednostráňové aplikaci na portálu pro registraci aplikací (pokud máte v registraci aplikace přidané jiné platformy, například web, je potřeba zajistit, aby se identifikátory URI přesměrování nepřekrývaly. Viz: Omezení identifikátoru URI přesměrování)
  • Abyste mohli spouštět aplikace v Internet Exploreru, musíte pro funkce ES6 poskytnout polyfilly, které MSAL.js spoléhá na (například přísliby).
  • Migrace aplikací Microsoft Entra do koncového bodu v2, pokud jste to ještě neudělali

Instalace a import knihovny MSAL

Knihovnu MSAL.js 2.x můžete nainstalovat dvěma způsoby:

Přes npm:

npm install @azure/msal-browser

V závislosti na systému modulů ho pak naimportujte, jak je znázorněno níže:

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

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

Přes CDN:

Načtěte skript v části záhlaví dokumentu 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>

Alternativní odkazy CDN a osvědčené postupy při používání CDN najdete v tématu: Využití CDN

Inicializace knihovny MSAL

V ADAL.js vytvoříte instanci třídy AuthenticationContext , která pak zveřejňuje metody, které můžete použít k dosažení ověřování (loginatd acquireTokenPopup .). Tento objekt slouží jako reprezentace připojení aplikace k autorizačnímu serveru nebo zprostředkovateli identity. Při inicializaci je jediným povinným parametrem clientId:

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

var authContext = new AuthenticationContext(config);

V MSAL.js vytvoříte instanci třídy PublicClientApplication . Stejně jako ADAL.js konstruktor očekává objekt konfigurace , který obsahuje clientId parametr minimálně. Další informace: Inicializace MSAL.js

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

const msalInstance = new msal.PublicClientApplication(msalConfig);

V ADAL.js i MSAL.js se identifikátor URI autority https://login.microsoftonline.com/common nastaví jako výchozí, pokud ho nezadáte.

Poznámka:

Pokud autoritu https://login.microsoftonline.com/common používáte ve verzi 2.0, umožníte uživatelům přihlásit se pomocí jakékoli organizace Microsoft Entra nebo osobního účtu Microsoft (MSA). Pokud chcete v MSAL.js omezit přihlášení k libovolnému účtu Microsoft Entra (stejné chování jako u ADAL.js), použijte https://login.microsoftonline.com/organizations místo toho.

Konfigurace KNIHOVNY MSAL

Některé z možností konfigurace v ADAL.js , které se používají při inicializaci AuthenticationContext , jsou v MSAL.js zastaralé, zatímco některé nové možnosti jsou zavedeny. Podívejte se na úplný seznam dostupných možností. Důležité je, že mnohé z těchto možností, s výjimkou clientIdmožnosti , je možné přepsat během získávání tokenů, což vám umožní nastavit je na základě jednotlivých požadavků . Můžete například použít jiný identifikátor URI autority nebo identifikátor URI přesměrování, než který jste nastavili při inicializaci při získávání tokenů.

Kromě toho už nemusíte specifikovat přihlašovací prostředí (to znamená, jestli používáte automaticky otevíraná okna nebo přesměrováváte stránku) prostřednictvím možností konfigurace. MSAL.js Místo toho zveřejňuje loginPopup a loginRedirect metody prostřednictvím PublicClientApplication instance.

Povolit protokolování

V ADAL.js nakonfigurujete protokolování samostatně na libovolném místě v kódu:

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)

V MSAL.js je protokolování součástí možností konfigurace a vytvoří se během inicializace 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);

Přepnutí na rozhraní MSAL API

Některé veřejné metody v ADAL.js mají ekvivalenty v MSAL.js:

ADAL MSAL Notes
acquireToken acquireTokenSilent Přejmenováno a nyní očekává objekt účtu
acquireTokenPopup acquireTokenPopup Teď async a vrátí příslib.
acquireTokenRedirect acquireTokenRedirect Teď async a vrátí příslib.
handleWindowCallback handleRedirectPromise Potřeba v případě použití prostředí pro přesměrování
getCachedUser getAllAccounts Přejmenováno a teď vrátí pole účtů.

Ostatní byli zastaralí, zatímco MSAL.js nabízí nové metody:

ADAL MSAL Notes
login Zastaralé Použití loginPopup nebo loginRedirect
logOut Zastaralé Použití logoutPopup nebo logoutRedirect
loginPopup
loginRedirect
logoutPopup
logoutRedirect
N/A getAccountByHomeId Filtruje účty podle ID domovské stránky (oid + ID tenanta)
getAccountLocalId Filtruje účty podle místního ID (užitečné pro ADFS)
getAccountUsername Filtruje účty podle uživatelského jména (pokud existuje)

Kromě toho, protože MSAL.js je implementováno v TypeScriptu na rozdíl od ADAL.js, zveřejňuje různé typy a rozhraní, které můžete využít ve svých projektech. Další informace najdete v referenčních informacích k MSAL.js rozhraní API.

Použití oborů místo prostředků

Důležitým rozdílem mezi koncovými body Azure Active Directory v1.0 a 2.0 je způsob přístupu k prostředkům. Při použití ADAL.js s koncovým bodem verze 1.0 byste nejprve zaregistrovali oprávnění na portálu pro registraci aplikací a pak požádali o přístupový token pro prostředek (například Microsoft Graph), jak je znázorněno níže:

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

MSAL.js podporuje pouze koncový bod verze 2.0 . Koncový bod v2.0 využívá model orientovaný na rozsah pro přístup k prostředkům. Proto při vyžádání přístupového tokenu pro prostředek musíte také zadat rozsah pro tento prostředek:

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

Jednou z výhod modelu zaměřeného na obor je schopnost používat dynamické obory. Při vytváření aplikací pomocí koncového bodu v1.0 jste museli zaregistrovat úplnou sadu oprávnění (označovanou jako statické obory), kterou aplikace vyžaduje, aby uživatel souhlasil v době přihlášení. V 2.0 můžete použít parametr oboru k vyžádání oprávnění v době, kdy je chcete (tedy dynamické obory). To uživateli umožňuje poskytnout přírůstkový souhlas s obory. Takže pokud na začátku chcete, aby se uživatel přihlásil k vaší aplikaci a nepotřebujete žádný druh přístupu, můžete to udělat. Pokud později potřebujete možnost číst kalendář uživatele, můžete požádat o rozsah kalendáře v metodách acquireToken a získat souhlas uživatele. Další informace: Prostředky a obory

Použití příslibů místo zpětných volání

V ADAL.js se zpětná volání po úspěšném ověření použijí pro jakoukoli operaci a získá se odpověď:

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

V MSAL.js se místo toho používají přísliby:

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

Můžete také použít syntaxi async/await , která je součástí ES8:

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

Ukládání tokenů do mezipaměti a načítání tokenů

Podobně jako ADAL.js MSAL.js ukládá tokeny do mezipaměti a další artefakty ověřování v úložišti prohlížeče pomocí rozhraní API webového úložiště. Doporučujeme použít sessionStorage možnost (viz: konfigurace), protože je bezpečnější při ukládání tokenů, které uživatelé získávají, ale localStorage poskytne vám Jednotné přihlašování na kartách a uživatelských relacích.

Důležité je, že nemáte přímý přístup k mezipaměti. Místo toho byste měli použít odpovídající rozhraní API MSAL.js pro načítání artefaktů ověřování, jako jsou přístupové tokeny nebo uživatelské účty.

Obnovení tokenů pomocí obnovovacích tokenů

ADAL.js používá implicitní tok OAuth 2.0, který nevrací obnovovací tokeny z bezpečnostních důvodů (obnovovací tokeny mají delší životnost než přístupové tokeny a jsou proto nebezpečné v rukou škodlivých herců). Proto ADAL.js provádí obnovení tokenu pomocí skrytého prvku IFrame, aby uživatel nebyl opakovaně vyzván k ověření.

S tokem ověřovacího kódu s podporou PKCE aplikace využívající MSAL.js 2.x získávají obnovovací tokeny spolu s ID a přístupovými tokeny, které je možné použít k jejich obnovení. Používání obnovovacích tokenů je abstrahované a vývojáři by s nimi neměli vytvářet logiku. Místo toho msAL spravuje prodlužování platnosti tokenů pomocí samotných obnovovacích tokenů. Předchozí mezipaměť tokenů s ADAL.js se nedá přenést do MSAL.js, protože schéma mezipaměti tokenů se změnilo a nekompatibilní se schématem použitým v ADAL.js.

Zpracování chyb a výjimek

Při použití MSAL.js je nejčastějším typem chyby, se kterou se můžete setkat interaction_in_progress , chyba. Tato chyba se vyvolá, když se vyvolá interaktivní rozhraní API (loginPopup, loginRedirectacquireTokenPopup, ), acquireTokenRedirectzatímco probíhá jiné interaktivní rozhraní API. Rozhraní login* API jsou asynchronní, acquireToken* takže budete muset před vyvoláním jiného příslibu zajistit, aby se výsledné přísliby vyřešily.

Další běžnou chybou je interaction_required. Tato chyba se často řeší zahájením interaktivní výzvy k získání tokenu. Například webové rozhraní API, ke kterým se pokoušíte získat přístup, může mít zavedené zásady podmíněného přístupu, které vyžadují, aby uživatel provedl vícefaktorové ověřování (MFA). V takovém případě se při zpracování interaction_required chyby aktivuje acquireTokenPopup nebo acquireTokenRedirect se uživateli zobrazí výzva k vícefaktorovém ověřování, což mu umožní ho zaplnit.

Další běžnou chybou, se kterou se můžete setkat, je consent_required, k níž dochází v případě, že uživatel nesouhlasí s oprávněními potřebnými k získání přístupového tokenu pro chráněný prostředek. Stejně jako v interaction_requiredpřípadě , řešení consent_required chyby často iniciuje interaktivní výzvu k získání tokenu pomocí nebo acquireTokenPopupacquireTokenRedirect.

Další informace: Běžné chyby MSAL.js a jejich zpracování

Použití rozhraní API pro události

MSAL.js (>=v2.4) zavádí rozhraní API událostí, které můžete ve svých aplikacích využívat. Tyto události se týkají procesu ověřování a toho, co služba MSAL provádí v libovolném okamžiku, a lze ji použít k aktualizaci uživatelského rozhraní, zobrazení chybových zpráv, kontrole, jestli probíhá nějaká interakce atd. Níže je například zpětné volání události, které se zavolá, když proces přihlášení z jakéhokoli důvodu selže:

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

Pro zajištění výkonu je důležité zrušit registraci zpětných volání událostí, když už nejsou potřeba. Další informace: rozhraní API MSAL.js událostí

Zpracování více účtů

ADAL.js má koncept uživatele , který představuje aktuálně ověřenou entitu. MSAL.js nahradí uživateleúčty vzhledem k tomu, že uživatel může mít přidružených více účtů. To také znamená, že teď potřebujete řídit více účtů a zvolit vhodný účet, se kterým chcete pracovat. Následující fragment kódu ukazuje tento proces:

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

Další informace najdete v tématu: Účty v MSAL.js

Použití knihoven obálky

Pokud vyvíjíte pro architektury Angular a React, můžete použít MSAL Angular v2 a MSAL React. Tyto obálky zpřístupňují stejné veřejné rozhraní API jako MSAL.js a nabízejí metody a komponenty specifické pro architekturu, které můžou zjednodušit procesy ověřování a získávání tokenů.

Spustit aplikaci

Po dokončení změn spusťte aplikaci a otestujte scénář ověřování:

npm start

Příklad: Zabezpečení spa pomocí ADAL.js vs. MSAL.js

Následující fragmenty kódu ukazují minimální kód potřebný pro jednostrákovou aplikaci, která ověřuje uživatele pomocí platformy Microsoft Identity Platform, a získání přístupového tokenu pro Microsoft Graph pomocí prvního ADAL.js a následného MSAL.js:

Použití ADAL.js Použití 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>

Další kroky