Come eseguire la migrazione di un'app JavaScript da ADAL.js a MSAL.js

Microsoft Authentication Library per JavaScript (MSAL.js, noto anche come msal-browser) 2.x è la libreria di autenticazione consigliata con le applicazioni JavaScript in Microsoft Identity Platform. Questo articolo evidenzia le modifiche necessarie per eseguire la migrazione di un'app che usa ADAL.js per usare MSAL.js 2.x

Nota

È consigliabile usare MSAL.js 2.x su MSAL.js 1.x. Il flusso di concessione del codice di autenticazione è più sicuro e consente alle applicazioni a pagina singola di mantenere un'esperienza utente ottimale nonostante le misure di privacy dei browser come Safari hanno implementato per bloccare i cookie di terze parti, tra gli altri vantaggi.

Prerequisiti

  • È necessario impostare il tipo di URL di risposta della piattaforma / su Applicazione a pagina singola nel portale di registrazione app (se sono state aggiunte altre piattaforme nella registrazione dell'app, ad esempio Web, è necessario assicurarsi che gli URI di reindirizzamento non si sovrappongano. Vedere: Restrizioni dell'URI di reindirizzamento)
  • È necessario fornire polyfill per le funzionalità di ES6 su cui MSAL.js si basa (ad esempio, promesse) per eseguire le app in Internet Explorer
  • Eseguire la migrazione delle app Microsoft Entra all'endpoint v2, se non è già stato fatto

Installare e importare MSAL

Esistono due modi per installare la libreria MSAL.js 2.x:

Tramite npm:

npm install @azure/msal-browser

Quindi, a seconda del sistema del modulo, importarlo come illustrato di seguito:

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

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

Tramite rete CDN:

Caricare lo script nella sezione intestazione del documento 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>

Per collegamenti alternativi rete CDN e procedure consigliate quando si usa rete CDN, vedere: rete CDN Utilizzo

Inizializzare MSAL

In ADAL.js si crea un'istanza della classe AuthenticationContext, che espone quindi i metodi che è possibile usare per ottenere l'autenticazione (loginacquireTokenPopupe così via). Questo oggetto funge da rappresentazione della connessione dell'applicazione al server di autorizzazione o al provider di identità. Durante l'inizializzazione, l'unico parametro obbligatorio è clientId:

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

var authContext = new AuthenticationContext(config);

In MSAL.js viene creata un'istanza della classe PublicClientApplication . Come ADAL.js, il costruttore prevede un oggetto di configurazione che contiene almeno il clientId parametro . Per altre informazioni, vedere Initialize MSAL.js (Inizializzare MSAL.js)

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

const msalInstance = new msal.PublicClientApplication(msalConfig);

In ADAL.js e MSAL.js, l'URI dell'autorità viene https://login.microsoftonline.com/common impostato per impostazione predefinita se non viene specificato.

Nota

Se si usa l'autorità https://login.microsoftonline.com/common nella versione 2.0, si consentirà agli utenti di accedere con qualsiasi organizzazione Microsoft Entra o con un account Microsoft personale . In MSAL.js, se si vuole limitare l'accesso a qualsiasi account Microsoft Entra (stesso comportamento di ADAL.js), usare https://login.microsoftonline.com/organizations invece .

Configurare MSAL

Alcune delle opzioni di configurazione in ADAL.js usate durante l'inizializzazione di AuthenticationContext sono deprecate in MSAL.js, mentre alcune nuove vengono introdotte. Vedere l'elenco completo delle opzioni disponibili. In particolare, molte di queste opzioni, ad eccezione clientIddi , possono essere sostituite durante l'acquisizione del token, consentendo di impostarle in base alle richieste . Ad esempio, è possibile usare un URI di autorità o un URI di reindirizzamento diverso da quello impostato durante l'inizializzazione durante l'acquisizione di token.

Inoltre, non è più necessario specificare l'esperienza di accesso , ovvero se si usano finestre popup o reindirizzando la pagina, tramite le opzioni di configurazione. Espone invece MSAL.js i loginPopup metodi e loginRedirect tramite l'istanza PublicClientApplication di .

Abilitazione della registrazione

In ADAL.js è possibile configurare la registrazione separatamente in qualsiasi posizione nel codice:

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 la registrazione fa parte delle opzioni di configurazione e viene creata durante l'inizializzazione di 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);

Passare all'API MSAL

Alcuni dei metodi pubblici in ADAL.js hanno equivalenti in MSAL.js:

ADAL MSAL Note
acquireToken acquireTokenSilent Rinominato e ora prevede un oggetto account
acquireTokenPopup acquireTokenPopup Ora asincrona e restituisce una promessa
acquireTokenRedirect acquireTokenRedirect Ora asincrona e restituisce una promessa
handleWindowCallback handleRedirectPromise Necessario se si usa l'esperienza di reindirizzamento
getCachedUser getAllAccounts Rinominato e ora restituisce una matrice di account.

Altri sono stati deprecati, mentre MSAL.js offre nuovi metodi:

ADAL MSAL Note
login N/D Deprecato. Usare loginPopup o loginRedirect
logOut N/D Deprecato. Usare logoutPopup o logoutRedirect
N/A loginPopup
N/D loginRedirect
N/D logoutPopup
N/D logoutRedirect
N/A getAccountByHomeId Filtra gli account in base all'ID home (oid + ID tenant)
N/D getAccountLocalId Filtra gli account in base all'ID locale (utile per ADFS)
N/D getAccountUsername Filtra gli account in base al nome utente (se esistente)

Inoltre, poiché MSAL.js viene implementato in TypeScript a differenza di ADAL.js, espone vari tipi e interfacce che è possibile usare nei progetti. Per altre informazioni, vedere le informazioni di riferimento sulle API MSAL.js.

Usare ambiti anziché risorse

Una differenza importante tra gli endpoint di Azure Active Directory v1.0 e 2.0 riguarda la modalità di accesso alle risorse. Quando si usa ADAL.js con l'endpoint v1.0 , è prima necessario registrare un'autorizzazione nel portale di registrazione delle app e quindi richiedere un token di accesso per una risorsa ,ad esempio Microsoft Graph, come illustrato di seguito:

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

MSAL.js supporta solo l'endpoint v2.0 . L'endpoint v2.0 usa un modello incentrato sull'ambito per accedere alle risorse. Pertanto, quando si richiede un token di accesso per una risorsa, è necessario specificare anche l'ambito per tale risorsa:

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

Uno dei vantaggi del modello incentrato sull'ambito è la possibilità di usare ambiti dinamici. Quando si compilano applicazioni usando l'endpoint v1.0, è necessario registrare il set completo di autorizzazioni (denominate ambiti statici) richiesto dall'applicazione per consentire all'utente di fornire il consenso al momento dell'accesso. Nella versione 2.0 è possibile usare il parametro di ambito per richiedere le autorizzazioni al momento desiderate (di conseguenza, ambiti dinamici). In questo modo l'utente può fornire il consenso incrementale agli ambiti. Pertanto, se all'inizio si vuole semplicemente che l'utente acceda all'applicazione e non è necessario alcun tipo di accesso, è possibile impostare questo tipo di autorizzazione. Se, in un secondo momento, si vuole avere la possibilità di leggere il calendario dell'utente, è possibile richiedere l'ambito del calendario nei metodi di acquisizione dei token e ottenere il consenso dell'utente. Per altre informazioni, vedere Risorse e ambiti

Usare promesse anziché callback

In ADAL.js i callback vengono usati per qualsiasi operazione dopo l'esito positivo dell'autenticazione e viene ottenuta una risposta:

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

In MSAL.js le promesse vengono invece usate:

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

È anche possibile usare la sintassi async/await fornita con ES8:

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

Memorizzare nella cache e recuperare i token

Come ADAL.js, MSAL.js memorizza nella cache i token e altri artefatti di autenticazione nell'archiviazione del browser, usando l'API web Archiviazione. È consigliabile usare sessionStorage l'opzione (vedere: configurazione) perché è più sicura nell'archiviazione dei token acquisiti dagli utenti, ma localStorage offre l'accesso Single Sign-On tra schede e sessioni utente.

Importante, non si dovrebbe accedere direttamente alla cache. È invece consigliabile usare un'API MSAL.js appropriata per recuperare elementi di autenticazione come token di accesso o account utente.

Rinnovare i token con token di aggiornamento

ADAL.js usa il flusso implicito OAuth 2.0, che non restituisce token di aggiornamento per motivi di sicurezza (i token di aggiornamento hanno una durata maggiore rispetto ai token di accesso e quindi sono più pericolosi nelle mani di utenti malintenzionati). Di conseguenza, ADAL.js esegue il rinnovo del token usando un IFrame nascosto in modo che all'utente non venga richiesto ripetutamente di eseguire l'autenticazione.

Con il flusso di codice di autenticazione con supporto PKCE, le app che usano MSAL.js 2.x ottengono token di aggiornamento insieme a ID e token di accesso, che possono essere usati per rinnovarli. L'uso dei token di aggiornamento viene astratto e gli sviluppatori non dovrebbero creare logica intorno a essi. MSAL gestisce invece il rinnovo dei token usando i token di aggiornamento. La cache dei token precedente con ADAL.js non sarà trasferiscibile a MSAL.js, perché lo schema della cache dei token è stato modificato e incompatibile con lo schema usato in ADAL.js.

Gestire errori ed eccezioni

Quando si usa MSAL.js, il tipo di errore più comune che si potrebbe riscontrare è l'errore interaction_in_progress . Questo errore viene generato quando viene richiamata un'API interattiva (loginPopup, loginRedirect, acquireTokenPopup, acquireTokenRedirect) mentre è ancora in corso un'altra API interattiva. Le login* API e acquireToken* sono asincrone , quindi è necessario assicurarsi che le promesse risultanti siano state risolte prima di richiamarne un'altra.

Un altro errore comune è interaction_required. Questo errore viene spesso risolto avviando una richiesta di acquisizione interattiva dei token. Ad esempio, l'API Web a cui si sta tentando di accedere potrebbe avere un criterio di accesso condizionale, che richiede all'utente di eseguire l'autenticazione a più fattori (MFA). In tal caso, la gestione dell'errore interaction_required attivando acquireTokenPopup o acquireTokenRedirect richiederà all'utente l'autenticazione a più fattori, consentendogli di completarla.

Un altro errore comune che potrebbe verificarsi è consent_required, che si verifica quando le autorizzazioni necessarie per ottenere un token di accesso per una risorsa protetta non vengono concesse dall'utente. Come in interaction_required, la soluzione per consent_required l'errore spesso avvia una richiesta di acquisizione di token interattiva, usando acquireTokenPopup o acquireTokenRedirect.

Per altre informazioni, vedere errori MSAL.js comuni e come gestirli

Usare l'API Eventi

MSAL.js (>=v2.4) introduce un'API degli eventi che è possibile usare nelle app. Questi eventi sono correlati al processo di autenticazione e alle operazioni eseguite da MSAL in qualsiasi momento e possono essere usati per aggiornare l'interfaccia utente, visualizzare i messaggi di errore, verificare se è in corso un'interazione e così via. Ad esempio, di seguito è riportato un callback di eventi che verrà chiamato quando il processo di accesso non riesce per qualsiasi motivo:

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

Per le prestazioni, è importante annullare la registrazione dei callback degli eventi quando non sono più necessari. Per altre informazioni, vedere l'API eventi MSAL.js

Gestire più account

ADAL.js ha il concetto di utente per rappresentare l'entità attualmente autenticata. MSAL.js sostituisce gli utenti con account, dato che un utente può avere più di un account associato. Ciò significa anche che è ora necessario controllare per più account e scegliere quello appropriato con cui lavorare. Il frammento di codice seguente illustra questo processo:

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

Per altre informazioni, vedere Account in MSAL.js

Usare le librerie wrapper

Se si sviluppano framework Angular e React, è possibile usare rispettivamente MSAL Angular v2 e MSAL React. Questi wrapper espongono la stessa API pubblica di MSAL.js offrendo al tempo stesso metodi e componenti specifici del framework che possono semplificare i processi di autenticazione e acquisizione di token.

Eseguire l'app

Al termine delle modifiche, eseguire l'app e testare lo scenario di autenticazione:

npm start

Esempio: Protezione di un'applicazione a pagina singola con ADAL.js e MSAL.js

I frammenti di codice seguenti illustrano il codice minimo necessario per un'applicazione a pagina singola che autentica gli utenti con Microsoft Identity Platform e ottiene un token di accesso per Microsoft Graph usando prima ADAL.js e quindi MSAL.js:

Uso di ADAL.js Uso di 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>

Passaggi successivi