Jak przeprowadzić migrację aplikacji JavaScript z biblioteki ADAL.js do biblioteki MSAL.js

Biblioteka uwierzytelniania firmy Microsoft dla języka JavaScript (MSAL.js, znana również jako msal-browser) 2.x to biblioteka uwierzytelniania zalecana w przypadku aplikacji JavaScript w Platforma tożsamości Microsoft. W tym artykule przedstawiono zmiany, które należy wprowadzić w celu przeprowadzenia migracji aplikacji korzystającej z biblioteki ADAL.js do korzystania z biblioteki MSAL.js 2.x

Uwaga

Zdecydowanie zalecamy używanie biblioteki MSAL.js 2.x przez bibliotekę MSAL.js 1.x. Przepływ udzielania kodu uwierzytelniania jest bezpieczniejszy i umożliwia aplikacjom jednostronicowym zachowanie dobrego środowiska użytkownika, mimo że przeglądarki ochrony prywatności, takie jak Safari, zaimplementowały blokowanie plików cookie innych firm, między innymi.

Wymagania wstępne

  • Musisz ustawić wartość Typ adresu URL odpowiedzi platformy / na aplikację jednostronicową w portalu rejestracji aplikacji (jeśli w rejestracji aplikacji dodano inne platformy, takie jak Sieć Web, musisz upewnić się, że identyfikatory URI przekierowania nie nakładają się na siebie. Zobacz: Ograniczenia identyfikatora URI przekierowania)
  • Aby uruchamiać aplikacje w programie Internet Explorer w programie Internet Explorer, należy podać polifills dla funkcji ES6, na których opiera się biblioteka MSAL.js (na przykład obietnice)
  • Migrowanie aplikacji Firmy Microsoft Entra do punktu końcowego w wersji 2, jeśli jeszcze tego nie zrobiono

Instalowanie i importowanie biblioteki MSAL

Istnieją dwa sposoby instalowania biblioteki MSAL.js 2.x:

Za pośrednictwem narzędzia npm:

npm install @azure/msal-browser

Następnie w zależności od systemu modułów zaimportuj go, jak pokazano poniżej:

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

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

Za pośrednictwem sieci CDN:

Załaduj skrypt w sekcji nagłówka 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>

Aby zapoznać się z alternatywnymi linkami i najlepszymi rozwiązaniami dotyczącymi sieci CDN w przypadku korzystania z sieci CDN, zobacz: Użycie usługi CDN

Inicjowanie biblioteki MSAL

W pliku ADAL.js utworzysz wystąpienie klasy AuthenticationContext, która następnie uwidacznia metody, których można użyć do uzyskania uwierzytelniania (loginacquireTokenPopupitp.). Ten obiekt służy jako reprezentacja połączenia aplikacji z serwerem autoryzacji lub dostawcą tożsamości. Podczas inicjowania jedynym obowiązkowym parametrem jest clientId:

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

var authContext = new AuthenticationContext(config);

W pliku MSAL.js zamiast tego utworzysz wystąpienie klasy PublicClientApplication . Podobnie jak biblioteka ADAL.js, konstruktor oczekuje obiektu konfiguracji zawierającego clientId parametr co najmniej. Zobacz, aby uzyskać więcej informacji: Inicjowanie biblioteki MSAL.js

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

const msalInstance = new msal.PublicClientApplication(msalConfig);

W plikach https://login.microsoftonline.com/common ADAL.js i MSAL.js identyfikator URI urzędu jest domyślnie ustawiony, jeśli go nie określisz.

Uwaga

Jeśli używasz https://login.microsoftonline.com/common urzędu w wersji 2.0, możesz zezwolić użytkownikom na logowanie się przy użyciu dowolnej organizacji firmy Microsoft Entra lub osobistego konta Microsoft (MSA). W pliku MSAL.js, jeśli chcesz ograniczyć logowanie do dowolnego konta Microsoft Entra (takie samo zachowanie jak w przypadku biblioteki ADAL.js), użyj https://login.microsoftonline.com/organizations zamiast tego.

Konfigurowanie biblioteki MSAL

Niektóre opcje konfiguracji w pliku ADAL.js używane podczas inicjowania elementu AuthenticationContext są przestarzałe w pliku MSAL.js, a niektóre nowe są wprowadzane. Zobacz pełną listę dostępnych opcji. Co ważne, wiele z tych opcji, z wyjątkiem clientId, można zastąpić podczas pozyskiwania tokenu, co pozwala ustawić je na podstawie poszczególnych żądań . Można na przykład użyć innego identyfikatora URI urzędu lub identyfikatora URI przekierowania niż identyfikator URI ustawiony podczas inicjowania podczas uzyskiwania tokenów.

Ponadto nie trzeba już określać środowiska logowania (niezależnie od tego, czy używasz okien podręcznych, czy przekierowania strony) za pośrednictwem opcji konfiguracji. Zamiast tego uwidacznia MSAL.jsloginPopup metody i loginRedirect metody za pośrednictwem PublicClientApplication wystąpienia.

Włącz rejestrowanie

W pliku ADAL.js rejestrowanie jest konfigurowane oddzielnie w dowolnym miejscu w kodzie:

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)

W pliku MSAL.js rejestrowanie jest częścią opcji konfiguracji i jest tworzone podczas inicjowania elementu 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);

Przełączanie do interfejsu API biblioteki MSAL

Niektóre metody publiczne w pliku ADAL.js mają odpowiedniki w pliku MSAL.js:

ADAL BIBLIOTEKA MSAL Uwagi
acquireToken acquireTokenSilent Zmieniono nazwę, a teraz oczekuje obiektu konta
acquireTokenPopup acquireTokenPopup Teraz asynchronizuj i zwraca obietnicę
acquireTokenRedirect acquireTokenRedirect Teraz asynchronizuj i zwraca obietnicę
handleWindowCallback handleRedirectPromise Wymagane w przypadku korzystania ze środowiska przekierowania
getCachedUser getAllAccounts Zmieniono nazwę, a teraz zwraca tablicę kont.

Inne były przestarzałe, a biblioteka MSAL.js oferuje nowe metody:

ADAL BIBLIOTEKA MSAL Uwagi
login Nie dotyczy Przestarzałe. Użyj loginPopup lub loginRedirect
logOut Nie dotyczy Przestarzałe. Użyj logoutPopup lub logoutRedirect
Brak loginPopup
NIE DOTYCZY loginRedirect
NIE DOTYCZY logoutPopup
NIE DOTYCZY logoutRedirect
Brak getAccountByHomeId Filtruje konta według identyfikatora domu (identyfikator oid i identyfikator dzierżawy)
Nie dotyczy getAccountLocalId Filtruje konta według identyfikatora lokalnego (przydatne w przypadku usług ADFS)
Nie dotyczy getAccountUsername Filtruje konta według nazwy użytkownika (jeśli istnieje)

Ponadto, ponieważ biblioteka MSAL.js jest implementowana w języku TypeScript w przeciwieństwie do biblioteki ADAL.js, udostępnia różne typy i interfejsy, których można używać w projektach. Aby uzyskać więcej informacji, zobacz dokumentację interfejsu API MSAL.js.

Używanie zakresów zamiast zasobów

Ważną różnicą między punktami końcowymi usługi Azure Active Directory w wersji 1.0 a 2.0 jest sposób uzyskiwania dostępu do zasobów. W przypadku korzystania z biblioteki ADAL.js z punktem końcowym w wersji 1.0 należy najpierw zarejestrować uprawnienie w portalu rejestracji aplikacji, a następnie zażądać tokenu dostępu dla zasobu (takiego jak Microsoft Graph), jak pokazano poniżej:

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

Biblioteka MSAL.js obsługuje tylko punkt końcowy w wersji 2.0 . Punkt końcowy w wersji 2.0 wykorzystuje model skoncentrowany na zakresie w celu uzyskania dostępu do zasobów. W związku z tym podczas żądania tokenu dostępu dla zasobu należy również określić zakres dla tego zasobu:

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

Jedną z zalet modelu skoncentrowanego na zakresie jest możliwość korzystania z zakresów dynamicznych. Podczas kompilowania aplikacji przy użyciu punktu końcowego w wersji 1.0 należy zarejestrować pełny zestaw uprawnień (nazywanych zakresami statycznymi) wymaganych przez aplikację, aby użytkownik wyraził zgodę w momencie logowania. W wersji 2.0 można użyć parametru zakresu, aby zażądać uprawnień w momencie ich użycia (w związku z tym zakresy dynamiczne). Dzięki temu użytkownik może udzielić przyrostowej zgody na zakresy. Jeśli więc na początku chcesz, aby użytkownik zalogował się do aplikacji i nie potrzebujesz żadnego rodzaju dostępu, możesz to zrobić. Jeśli później potrzebujesz możliwości odczytania kalendarza użytkownika, możesz zażądać zakresu kalendarza w metodach acquireToken i uzyskać zgodę użytkownika. Zobacz, aby uzyskać więcej informacji: Zasoby i zakresy

Używanie obietnic zamiast wywołań zwrotnych

W pliku ADAL.js wywołania zwrotne są używane dla dowolnej operacji po pomyślnym uwierzytelnieniu i uzyskana jest odpowiedź:

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

W pliku MSAL.js zamiast tego są używane obietnice:

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

Można również użyć składni async/await , która jest dostarczana z ES8:

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

Buforowanie i pobieranie tokenów

Podobnie jak biblioteka ADAL.js, tokeny MSAL.js buforuje i inne artefakty uwierzytelniania w magazynie przeglądarki przy użyciu interfejsu API usługi Web Storage. Zalecamy użycie sessionStorage opcji (zobacz: konfiguracja), ponieważ jest ona bezpieczniejsza w przechowywaniu tokenów nabytych przez użytkowników, ale localStorage daje Logowanie jednokrotne na kartach i sesjach użytkowników.

Co ważne, nie należy uzyskiwać bezpośredniego dostępu do pamięci podręcznej. Zamiast tego należy użyć odpowiedniego interfejsu API MSAL.js do pobierania artefaktów uwierzytelniania, takich jak tokeny dostępu lub konta użytkowników.

Odnawianie tokenów przy użyciu tokenów odświeżania

Biblioteka ADAL.js używa niejawnego przepływu OAuth 2.0, który nie zwraca tokenów odświeżania ze względów bezpieczeństwa (tokeny odświeżania mają dłuższy okres istnienia niż tokeny dostępu i dlatego są bardziej niebezpieczne w rękach złośliwych podmiotów). W związku z tym biblioteka ADAL.js przeprowadza odnawianie tokenu przy użyciu ukrytego elementu IFrame, aby użytkownik nie był wielokrotnie monitowany o uwierzytelnienie.

Dzięki przepływowi kodu uwierzytelniania z obsługą protokołu PKCE aplikacje korzystające z biblioteki MSAL.js 2.x uzyskują tokeny odświeżania wraz z identyfikatorami i tokenami dostępu, których można użyć do ich odnowienia. Użycie tokenów odświeżania jest abstrakcje, a deweloperzy nie powinni tworzyć wokół nich logiki. Zamiast tego biblioteka MSAL zarządza odnawianiem tokenu przy użyciu tokenów odświeżania. Poprzednia pamięć podręczna tokenów z biblioteką ADAL.js nie będzie można przenieść do biblioteki MSAL.js, ponieważ schemat pamięci podręcznej tokenu został zmieniony i niezgodny ze schematem używanym w pliku ADAL.js.

Obsługa błędów i wyjątków

W przypadku korzystania z biblioteki MSAL.js najczęstszym typem błędu może być interaction_in_progress błąd. Ten błąd jest zgłaszany, gdy interakcyjny interfejs API (loginPopup, loginRedirect, acquireTokenPopup, acquireTokenRedirect) jest wywoływany, gdy inny interaktywny interfejs API jest nadal w toku. Interfejsy login* API i acquireToken*asynchroniczne , dlatego należy upewnić się, że wynikowe obietnice zostały rozwiązane przed wywołaniem innego.

Innym typowym błędem jest interaction_required. Ten błąd jest często rozwiązywany przez zainicjowanie interakcyjnego monitu o uzyskanie tokenu. Na przykład internetowy interfejs API, do którego próbujesz uzyskać dostęp, może mieć zasady dostępu warunkowego, co wymaga od użytkownika przeprowadzenia uwierzytelniania wieloskładnikowego (MFA). W takim przypadku obsługa interaction_required błędu przez wyzwolenie lub acquireTokenRedirect wyświetlenie monitu użytkownika o uwierzytelnianie wieloskładnikoweacquireTokenPopup, co umożliwi mu pełne filtrowanie.

Kolejnym typowym błędem, który może wystąpić, jest consent_required, który występuje, gdy uprawnienia wymagane do uzyskania tokenu dostępu dla chronionego zasobu nie są wyrażane przez użytkownika. Podobnie jak w interaction_requiredsystemie rozwiązanie błędu consent_required często inicjuje interakcyjny monit o pozyskiwanie tokenów przy użyciu polecenia acquireTokenPopup lub acquireTokenRedirect.

Zobacz więcej: Typowe błędy MSAL.js i sposób ich obsługi

Korzystanie z interfejsu API zdarzeń

Biblioteka MSAL.js (>=v2.4) wprowadza interfejs API zdarzeń, którego można używać w aplikacjach. Te zdarzenia są związane z procesem uwierzytelniania i działaniem biblioteki MSAL w dowolnym momencie i mogą służyć do aktualizowania interfejsu użytkownika, wyświetlania komunikatów o błędach, sprawdzania, czy jakakolwiek interakcja jest w toku itd. Na przykład poniżej znajduje się wywołanie zwrotne zdarzeń, które będzie wywoływane, gdy proces logowania zakończy się niepowodzeniem z jakiegokolwiek powodu:

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

W przypadku wydajności ważne jest wyrejestrowanie wywołań zwrotnych zdarzeń, gdy nie są już potrzebne. Zobacz, aby uzyskać więcej informacji: interfejs API zdarzeń MSAL.js

Obsługa wielu kont

Biblioteka ADAL.js ma pojęcie użytkownika do reprezentowania aktualnie uwierzytelnionej jednostki. Biblioteka MSAL.js zastępuje użytkowników kontami, biorąc pod uwagę fakt, że użytkownik może mieć skojarzone z nimi więcej niż jedno konto. Oznacza to również, że teraz musisz kontrolować wiele kont i wybrać odpowiednie do pracy. Poniższy fragment kodu ilustruje ten 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
      }
  }
}

Aby uzyskać więcej informacji, zobacz: Accounts in MSAL.js (Konta w pliku MSAL.js)

Korzystanie z bibliotek otoki

Jeśli opracowujesz platformy Angular i React, możesz użyć odpowiednio bibliotek MSAL Angular v2 i MSAL React. Te otoki uwidaczniają ten sam publiczny interfejs API co MSAL.js, oferując jednocześnie metody i składniki specyficzne dla platformy, które mogą usprawnić procesy uwierzytelniania i pozyskiwania tokenów.

Uruchom aplikację

Po zakończeniu zmian uruchom aplikację i przetestuj scenariusz uwierzytelniania:

npm start

Przykład: Zabezpieczanie SPA za pomocą biblioteki ADAL.js a MSAL.js

Poniższe fragmenty kodu przedstawiają minimalny kod wymagany dla aplikacji jednostronicowej uwierzytelniającej użytkowników przy użyciu Platforma tożsamości Microsoft i uzyskiwania tokenu dostępu dla programu Microsoft Graph przy użyciu pierwszej biblioteki ADAL.js, a następnie biblioteki MSAL.js:

Korzystanie z biblioteki ADAL.js Korzystanie z biblioteki 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>

Następne kroki