Udostępnij za pośrednictwem


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 w wersji 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, które MSAL.js polegają na (na przykład obietnicach)
  • 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 ADAL.js utworzysz wystąpienie klasy AuthenticationContext , która następnie uwidacznia metody, których można użyć do osiągnięcia uwierzytelniania (loginitd acquireTokenPopup .). 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. Zobacz, aby uzyskać więcej informacji: Inicjowanie MSAL.js

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

const msalInstance = new msal.PublicClientApplication(msalConfig);

W ADAL.js i MSAL.js identyfikator URI urzędu jest https://login.microsoftonline.com/common 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 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.

Konfigurowanie biblioteki 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 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.js loginPopup metody i loginRedirect metody za pośrednictwem PublicClientApplication wystąpienia.

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łączanie do interfejsu API biblioteki 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 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, 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
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 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 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 ADAL.js wywołania zwrotne są używane dla każdej operacji po pomyślnym uwierzytelnieniu i uzyskana jest odpowiedź:

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

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 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 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 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, który może wystąpić, jest 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ń

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

ADAL.js ma pojęcie użytkownika reprezentującego 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 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: Konta w 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 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