Udostępnij przez


Jak migrować aplikację JavaScript z ADAL.js do 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 aplikacjach 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 ADAL.js do używania MSAL.js 2.x

Uwaga

Zdecydowanie zalecamy MSAL.js 2.x zamiast 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ć Platformę / Typ adresu URL odpowiedzi na aplikację jednostronicową w portalu rejestracji aplikacji (jeśli w rejestracji aplikacji dodano inne platformy, takie jak Sieć Web, musisz upewnić się, że URI przekierowania się nie nakładają. Zobacz: Ograniczenia URI przekierowania)
  • Aby uruchamiać aplikacje w Internet Explorerze, musisz dostarczyć polyfills dla funkcji ES6, na których polega MSAL.js (na przykład funkcji Promise).
  • Migruj swoje aplikacje Microsoft Entra do punktu końcowego v2, jeśli jeszcze tego nie zrobiłeś.

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 MSAL

W ADAL.js tworzysz instancję klasy AuthenticationContext, która następnie udostępnia metody, które można zastosować do uwierzytelniania (login, acquireTokenPopup itp.). 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 MSAL.js zamiast tego utworzysz wystąpienie klasy PublicClientApplication . Podobnie jak ADAL.js, konstruktor oczekuje obiektu konfiguracji, który zawiera clientId parametr co najmniej. Więcej informacji znajdziesz tutaj: Inicjowanie MSAL.js

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

const msalInstance = new msal.PublicClientApplication(msalConfig);

W ADAL.js i MSAL.js adres URI autorytetu jest domyślnie ustawiony na https://login.microsoftonline.com/common, jeśli go nie określisz.

Uwaga

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

Konfiguracja MSAL

Niektóre opcje konfiguracji w ADAL.js, które są używane podczas inicjowania authenticationContext są przestarzałe w MSAL.js, podczas gdy 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 autoryzacji lub identyfikatora URI przekierowania niż ten ustawiony podczas inicjowania przy uzyskiwaniu tokenów.

Ponadto nie musisz już określać sposobu logowania (czyli, czy używasz okien podręcznych, czy przekierowania strony) w opcjach konfiguracji. Zamiast tego udostępnia metody MSAL.js, loginPopup i loginRedirect za pośrednictwem wystąpienia PublicClientApplication.

Włącz rejestrowanie

W 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 MSAL.js rejestrowanie jest częścią opcji konfiguracji i jest tworzone podczas inicjowania programu 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łącz na interfejs API MSAL

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

ADAL Biblioteka MSAL Uwagi
acquireToken acquireTokenSilent Zmieniono nazwę, a teraz oczekuje obiektu konta
acquireTokenPopup acquireTokenPopup Teraz działa asynchronicznie i zwraca obiekt Promise
acquireTokenRedirect acquireTokenRedirect Teraz działa asynchronicznie i zwraca obiekt Promise
handleWindowCallback handleRedirectPromise Wymagane w przypadku korzystania z doświadczenia przekierowania
getCachedUser getAllAccounts Zmieniono nazwę, a teraz zwraca tablicę kont.

Inne były przestarzałe, podczas gdy 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
Nie dotyczy loginPopup
Nie dotyczy loginRedirect
Nie dotyczy logoutPopup
Nie dotyczy logoutRedirect
Nie dotyczy getAccountByHomeId Filtruje konta według identyfikatora domu (OID i identyfikatora tenanta)
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 MSAL.js jest implementowany w języku TypeScript w przeciwieństwie do 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 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
});

MSAL.js obsługuje tylko punkt końcowy v2.0. Punkt końcowy v2.0 wykorzystuje model zorientowany 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żesz użyć parametru zakresu, aby zażądać uprawnień w momencie, gdy ich potrzebujesz (stąd 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 ADAL.js funkcje zwrotne są używane do każdej operacji po pomyślnym uwierzytelnieniu i uzyskaniu odpowiedzi.

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

W 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 ADAL.js, MSAL.js buforuje tokeny i inne artefakty uwierzytelniania w magazynie przeglądarki przy użyciu interfejsu API 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.

Odnów tokeny za pomocą tokenów odświeżania

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 ważności niż tokeny dostępu, przez co są bardziej niebezpieczne w przypadku dostania się w ręce złośliwych podmiotów). W związku z tym 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 MSAL.js 2.x uzyskują tokeny odświeżania wraz z tokenami ID i tokenami dostępu, które można wykorzystać do ich odnowienia. Użycie tokenów odświeżania jest zatajone, a programiści nie powinni tworzyć wokół nich logiki. Zamiast tego, biblioteka MSAL samodzielnie zarządza odnawianiem tokenów przy użyciu tokenów odświeżania. Poprzednia pamięć podręczna tokenów z ADAL.js nie będzie można przenieść do MSAL.js, ponieważ schemat pamięci podręcznej tokenu został zmieniony i niezgodny ze schematem używanym w ADAL.js.

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

W przypadku korzystania z MSAL.js najczęstszym typem błędu, jaki może wystąpić, jest błąd interaction_in_progress. 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 API login* i acquireToken*asynchroniczne, dlatego należy upewnić się, że zwracane obietnice zostały spełnione przed wywołaniem kolejnej.

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 acquireTokenPopup lub acquireTokenRedirect wywoła monit o uwierzytelnianie wieloskładnikowe użytkownika, co umożliwi mu jego dokończenie.

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_required, rozwiązanie błędu consent_required często polega na inicjowaniu interaktywnego monitu o pozyskanie tokenu przy użyciu acquireTokenPopup lub acquireTokenRedirect.

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

Korzystanie z interfejsu API zdarzeń

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 zdarzenia, które będzie wywoływane, gdy proces logowania zakończy się niepowodzeniem z dowolnego 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

ADAL.js posługuje się pojęciem użytkownika do reprezentowania obecnie uwierzytelnionej jednostki. 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, z którym chcesz pracować. 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: Konta w MSAL.js

Użyj bibliotek otoki

Jeśli tworzysz aplikacje w frameworkach 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 przy użyciu ADAL.js a MSAL.js

Poniższe fragmenty kodu pokazują 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 najpierw ADAL.js, a następnie MSAL.js:

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