Gewusst wie: Migrieren einer JavaScript-App von ADAL.js zu MSAL.js

Microsoft-Authentifizierungsbibliothek für JavaScript (MSAL.js, auch als msal-browser bezeichnet) 2.x ist die Authentifizierungsbibliothek, die mit JavaScript-Anwendungen auf der Microsoft-Identitätsplattform verwendet werden sollte. In diesem Artikel werden die Änderungen hervorgehoben, die Sie vornehmen müssen, um eine App zu migrieren, die ADAL.js zur Verwendung von MSAL.js 2.x einsetzt.

Hinweis

Sie sollten unbedingt MSAL.js 2.x statt MSAL.js 1.x verwenden. Der Ablauf der Gewährung von Authentifizierungscode ist sicherer und ermöglicht Single-Page-Anwendungen neben anderen Vorteilen, trotz der Datenschutzmaßnahmen, die in Browsern wie Safari implementiert sind, um Cookies von Drittanbietern zu blockieren, ein hohes Maß an Benutzerfreundlichkeit zu bieten.

Voraussetzungen

  • Sie müssen für Plattform / Antwort-URL-Typ im App-Registrierungsportal Single-Page-Anwendung einstellen. (Wenn Sie andere Plattformen wie Web in Ihrer App-Registrierung hinzugefügt haben, müssen Sie sicherstellen, dass sich die Umleitungs-URIs nicht überlappen. Informationen hierzu finden Sie unter Einschränkungen bei Umleitungs-URIs.)
  • Sie müssen polyfills für ES6-Funktionen bereitstellen, von denen MSAL.js abhängig ist (z. B. Zusagen), damit Ihre Apps im Internet Explorer ausgeführt werden können.
  • Migrieren Sie Ihre Microsoft Entra-Apps zum v2-Endpunkt, falls dies noch nicht erfolgt ist.

Installieren und Importieren von MSAL

Es gibt zwei Möglichkeiten, die MSAL.js-2.x-Bibliothek zu installieren:

Über npm:

npm install @azure/msal-browser

Importieren Sie sie dann je nach Modulsystem wie unten gezeigt:

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

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

Über CDN:

Laden Sie das Skript im Headerabschnitt Ihres HTML-Dokuments:

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

Alternative CDN-Links und bewährte Methoden bei der Verwendung von CDN finden Sie unter CDN Usage (CDN-Nutzung).

MSAL initialisieren

In ADAL.js instanziieren Sie die Klasse AuthenticationContext, die dann die Methoden bereitstellt, die Sie für die Authentifizierung verwenden können (login, acquireTokenPopup usw.). Dieses Objekt dient als Darstellung der Verbindung Ihrer Anwendung mit dem Autorisierungsserver oder Identitätsanbieter. Bei der Initialisierung ist die clientId der einzige obligatorische Parameter:

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

var authContext = new AuthenticationContext(config);

In MSAL.js instanziieren Sie stattdessen die Klasse PublicClientApplication. Wie ADAL.js erwartet der Konstruktor ein Konfigurationsobjekt, das mindestens den Parameter clientId enthält. Weitere Informationen finden Sie unter Initialize MSAL.js.

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

const msalInstance = new msal.PublicClientApplication(msalConfig);

Sowohl in ADAL.js als auch MSAL.js wird der Autoritäts-URI standardmäßig auf https://login.microsoftonline.com/common festgelegt, wenn Sie ihn nicht angeben.

Hinweis

Bei Verwendung dieser Autorität (https://login.microsoftonline.com/common) in v2.0 geben Sie Benutzer*innen die Möglichkeit, sich mit einer beliebigen Microsoft Entra-Organisation oder einem persönlichen Microsoft-Konto (Microsoft Account, MSA) anzumelden. Wenn Sie in MSAL.js Anmeldungen auf ein Microsoft Entra-Konto beschränken möchten (dasselbe Verhalten wie bei ADAL.js), verwenden Sie stattdessen https://login.microsoftonline.com/organizations.

Konfigurieren von MSAL

Einige der Konfigurationsoptionen in ADAL.js, die bei der Initialisierung von AuthenticationContext verwendet werden, sind in MSAL.js veraltet, während einige neue Optionen eingeführt wurden. Weitere Informationen finden Sie in der vollständigen Liste der verfügbaren Optionen. Wichtig ist, dass viele dieser Optionen mit Ausnahme von clientId während des Tokenerwerbs außer Kraft gesetzt werden können, so dass Sie sie für jede Anforderung individuell festlegen können. Sie können zum Beispiel einen anderen Autoritäts-URI oder Umleitungs-URI verwenden als den, den Sie während der Initialisierung beim Erwerb von Token festgelegt haben.

Darüber hinaus müssen Sie die Anmeldebenutzeroberfläche (d. h., ob Popupfenster verwendet werden oder die Seite umgeleitet wird) nicht mehr über die Konfigurationsoptionen festlegen. Stattdessen stellt MSAL.jsloginPopup- und loginRedirect-Methoden über die PublicClientApplication-Instanz zur Verfügung.

Aktivieren der Protokollierung

In ADAL.js konfigurieren Sie die Protokollierung separat an einer beliebigen Stelle im Code:

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)

In MSAL.js ist die Protokollierung Teil der Konfigurationsoptionen und wird während der Initialisierung von PublicClientApplication erstellt:

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

Wechseln zur MSAL-API

Die meisten öffentlichen Methoden in ADAL.js verfügen über Entsprechungen in MSAL.js:

ADAL MSAL Notizen
acquireToken acquireTokenSilent Wurde umbenannt und erwartet nun ein account-Objekt
acquireTokenPopup acquireTokenPopup Jetzt asynchron, gibt außerdem eine Zusage zurück.
acquireTokenRedirect acquireTokenRedirect Jetzt asynchron, gibt außerdem eine Zusage zurück.
handleWindowCallback handleRedirectPromise Erforderlich bei Verwendung der Umleitung
getCachedUser getAllAccounts Umbenannt und gibt jetzt ein Array von Konten zurück.

Andere sind veraltet, während MSAL.js neue Methoden bietet:

ADAL MSAL Notizen
login Veraltet. loginPopup oder loginRedirect verwenden
logOut Veraltet. logoutPopup oder logoutRedirect verwenden
loginPopup
loginRedirect
logoutPopup
logoutRedirect
getAccountByHomeId Filtert Konten nach Home-ID (oid + Mandanten-ID)
getAccountLocalId Filtert Konten nach lokaler ID (nützlich für ADFS)
getAccountUsername Filtert Konten nach Benutzername (falls vorhanden)

Da MSAL.js im Gegensatz zu ADAL.js in TypeScript implementiert wird, werden außerdem verschiedene Typen und Schnittstellen verfügbar gemacht, die Sie in Ihren Projekten verwenden können. Ausführlichere Informationen finden Sie in der MSAL.js-API-Referenz.

Verwenden von Bereichen anstelle von Ressourcen

Ein wichtiger Unterschied zwischen Endpunkten von Azure Active Directory V1.0 und V2.0 besteht darin, wie der Zugriff auf die Ressourcen erfolgt. Bei Verwendung von ADAL.js mit dem v1.0-Endpunkt registrieren Sie zunächst eine Berechtigung im App-Registrierungsportal und fordern dann wie unten gezeigt ein Zugriffstoken für eine Ressource an (z. B. Microsoft Graph):

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

MSAL.js unterstützt nur den v2.0-Endpunkt. Im v2.0-Endpunkt wird für den Zugriff auf Ressourcen ein bereichsbezogenes Modell verwendet. Wenn Sie also ein Zugriffstoken für eine Ressource anfordern, müssen Sie auch den Bereich für die Ressource angeben:

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

Ein Vorteil des bereichsbezogenen Modells ist die Möglichkeit, dynamische Bereiche zu verwenden. Beim Erstellen von Anwendungen mit dem v1.0-Endpunkt mussten Sie den vollständigen Satz der in der Anwendung erforderlichen Berechtigungen (sogenannte statische Bereiche) registrieren, damit bei der Anmeldung die Benutzereinwilligung eingeholt werden konnte. In v2.0 können Sie die Berechtigungen über den scope-Parameter zum gewünschten Zeitpunkt anfordern (daher dynamische Bereiche). Dadurch kann der Benutzer Bereichen inkrementelle Einwilligungen erteilen. Wenn Sie also am Anfang nur möchten, dass sich der Benutzer bei Ihrer Anwendung anmeldet und Sie keinen Zugriff benötigen, können Sie dies tun. Wenn Sie später in der Lage sein müssen, den Kalender des Benutzers zu lesen, können Sie dann den Kalendergeltungsbereich in den Tokenabrufmethoden („acquireToken“) anfordern und die Einwilligung des Benutzers einholen. Weitere Informationen finden Sie unter Resources and scopes (Ressourcen und Bereiche).

Verwenden von Zusagen anstelle von Rückrufen

In ADAL.js werden Rückrufe für jeden Vorgang verwendet, nachdem die Authentifizierung erfolgreich war, und es wird eine Antwort abgerufen:

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

In MSAL.js werden stattdessen Zusagen verwendet:

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

Sie können außerdem die in ES8 enthaltene async/await-Syntax verwenden:

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

Zwischenspeichern und Abrufen von Token

Wie ADAL.js speichert MSAL.js Token und andere Authentifizierungsartefakte mithilfe der Web-Storage-API im Browserspeicher zwischen. Sie sollten die sessionStorage-Option (siehe: Konfiguration) verwenden, da sie sicherer ist, was die Speicherung von Token angeht, die von Ihren Benutzern erworben werden, aber localStorage ermöglicht Ihnen Registerkarten und Benutzersitzungen übergreifend Einmaliges Anmelden.

Wichtig: Sie sollten nicht direkt auf den Cache zugreifen. Stattdessen sollten Sie eine geeignete MSAL.js-API zum Abrufen von Authentifizierungsartefakten wie Zugriffstoken oder Benutzerkonten verwenden.

Erneuern von Token mit Aktualisierungstoken

ADAL.js verwendet den impliziten OAuth 2.0-Fluss, der aus Sicherheitsgründen keine Aktualisierungstoken zurückgibt (Aktualisierungstoken haben eine längere Lebensdauer als Zugriffstoken und sind daher in den Händen böswilliger Akteure gefährlicher). Daher führt ADAL.js die Tokenerneuerung mithilfe eines ausgeblendeten IFrames aus, sodass der Benutzer nicht wiederholt zur Authentifizierung aufgefordert wird.

Mit dem Authentifizierungscodeflow mit PKCE-Unterstützung erhalten Apps, die MSAL.js 2.x verwenden, Aktualisierungstoken zusammen mit ID- und Zugriffstoken, die zum Erneuern verwendet werden können. Die Verwendung von Aktualisierungstoken wird abstrahiert, und die Entwickler sollten keine Logik um sie herum erstellen. Stattdessen verwaltet MSAL die Tokenerneuerung mithilfe von Aktualisierungstoken selbst. Ihr vorheriger Tokencache mit ADAL.js kann nicht auf MSAL.js übertragen werden, da sich das Tokencacheschema geändert hat und nicht mit dem in ADAL.js verwendeten Schema kompatibel ist.

Behandeln von Fehlern und Ausnahmen

Bei Verwendung von MSAL.js tritt der Fehler interaction_in_progress am häufigsten auf. Dieser Fehler wird ausgelöst, wenn eine interaktive API (loginPopup, loginRedirect, acquireTokenPopup, acquireTokenRedirect) aufgerufen wird, während noch eine andere interaktive API ausgeführt wird. Die login*- und acquireToken*-API sind asynchron, sodass Sie sicherstellen müssen, dass die resultierenden Zusagen aufgelöst wurden, bevor Sie eine weitere aufrufen.

Ein weiterer häufiger Fehler ist interaction_required. Dieser Fehler kann häufig durch Initiierung einer interaktiven Tokenerfassungsaufforderung behoben werden. Beispielsweise kann für die Web-API, auf die Sie zugreifen möchten, eine Richtlinie für bedingten Zugriff gelten, die erfordert, dass der Benutzer die mehrstufige Authentifizierung (Multi-Factor Authentication, MFA) durchführen muss. In diesem Fall fordert die Behandlung des interaction_required-Fehlers durch Auslösen von acquireTokenPopup oder acquireTokenRedirect den Benutzer zur MFA auf und ermöglicht ihm die Durchführung.

Ein weiterer häufiger Fehler ist consent_required. Er tritt auf, wenn Berechtigungen, die zum Abrufen eines Zugriffstokens für eine geschützte Ressource erforderlich sind, vom Benutzer nicht bewilligt werden. Wie bei interaction_required ist die Lösung für den consent_required-Fehler häufig das Initiieren einer interaktiven Tokenbeschaffungsaufforderung mithilfe der acquireTokenPopup- oder acquireTokenRedirect-Methode.

Weitere Informationen zu häufigen MSAL.js-Fehlern und ihrer Behandlung finden Sie hier.

Verwenden der Ereignis-API

MSAL.js (>=v2.4) führt eine Ereignis-API ein, die Sie in Ihren Apps verwenden können. Diese Ereignisse beziehen sich auf den Authentifizierungsprozess und die Aktivitäten von MSAL zu einem beliebigen Zeitpunkt und können verwendet werden, um die Benutzeroberfläche zu aktualisieren, Fehlermeldungen anzuzeigen, zu überprüfen, ob eine Interaktion ausgeführt wird, usw. Im Folgenden finden Sie beispielsweise einen Ereignisrückruf, der aufgerufen wird, wenn beim Anmeldevorgang aus beliebigem Grund ein Fehler auftritt:

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

Der Leistung wegen ist es wichtig, die Registrierung von Ereignisrückrufen aufzuheben, wenn sie nicht mehr benötigt werden. Weitere Informationen finden Sie unter Events (Ereignisse).

Behandeln mehrerer Konten

Im Konzept von ADAL.js stellt ein Benutzer die aktuell authentifizierte Entität dar. MSAL.js ersetzt Benutzer durch Konten, da einem Benutzer mehrere Konten zugeordnet werden können. Dies bedeutet auch, dass Sie jetzt mehrere Konten steuern und das am besten geeignete Konto auswählen müssen, um damit zu arbeiten. Der folgende Codeausschnitt veranschaulicht diesen Prozess:

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

Weitere Informationen finden Sie unter Accounts in MSAL.js (Konten in MSAL.js).

Verwenden der Wrapperbibliotheken

Wenn Sie für Angular- und React-Frameworks entwickeln, können Sie MSAL Angular v2 bzw. MSAL React verwenden. Diese Wrapper machen die gleiche öffentliche API verfügbar wie MSAL.js und bieten frameworkspezifische Methoden und Komponenten, die die Authentifizierungs- und Tokenerfassungsprozesse optimieren können.

Ausführen der App

Nachdem Sie die gewünschten Änderungen vorgenommen haben, können Sie die App ausführen und das Authentifizierungsszenario testen:

npm start

Beispiel: Sichern einer SPA mit ADAL.js im Vergleich zu MSAL.js

Die folgenden Codeausschnitte zeigen den minimal erforderlichen Code für eine Single-Page-Anwendung, die Benutzer mit der Microsoft Identity Platform authentifiziert und ein Zugriffstoken für Microsoft Graph abruft, indem zuerst ADAL.js und dann MSAL.js verwendet wird:

Verwenden von ADAL.js Verwenden von 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>

Nächste Schritte