Migrera en JavaScript-app från ADAL.js till MSAL.js

Microsoft Authentication Library for JavaScript (MSAL.js, även kallat msal-browser) 2.x är det autentiseringsbibliotek som vi rekommenderar att du använder med JavaScript-program på Microsofts identitetsplattform. Den här artikeln belyser de ändringar du behöver göra för att migrera en app som använder ADAL.js för att använda MSAL.js 2.x

Kommentar

Vi rekommenderar starkt MSAL.js 2.x över MSAL.js 1.x. Autentiseringskodens beviljandeflöde är säkrare och gör det möjligt för ensidesprogram att upprätthålla en bra användarupplevelse trots de sekretessmått webbläsare som Safari har implementerat för att blockera cookies från tredje part, bland andra fördelar.

Förutsättningar

  • Du måste ange typ av plattformssvars-URL / till Ensidesprogram på appregistreringsportalen (om du har lagt till andra plattformar i appregistreringen, till exempel webb, måste du se till att omdirigerings-URI:erna inte överlappar varandra. Se: Omdirigerings-URI-begränsningar)
  • Du måste ange polyfiller för ES6-funktioner som MSAL.js förlitar sig på (till exempel löften) för att kunna köra dina appar i Internet Explorer
  • Migrera dina Microsoft Entra-appar till v2-slutpunkten om du inte redan har gjort det

Installera och importera MSAL

Det finns två sätt att installera MSAL.js 2.x-biblioteket:

Via npm:

npm install @azure/msal-browser

Beroende på ditt modulsystem importerar du det sedan enligt nedan:

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

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

Via CDN:

Läs in skriptet i rubrikavsnittet i HTML-dokumentet:

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

Alternativa CDN-länkar och metodtips när du använder CDN finns i: CDN-användning

Initiera MSAL

I ADAL.js instansierar du klassen AuthenticationContext, som sedan exponerar de metoder som du kan använda för att uppnå autentisering (acquireTokenPopuploginosv.). Det här objektet fungerar som en representation av programmets anslutning till auktoriseringsservern eller identitetsprovidern. När du initierar är den enda obligatoriska parametern clientId:

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

var authContext = new AuthenticationContext(config);

I MSAL.js instansierar du klassen PublicClientApplication i stället. Precis som ADAL.js förväntar sig konstruktorn ett konfigurationsobjekt som innehåller parametern clientId som minst. Mer information finns i: Initiera MSAL.js

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

const msalInstance = new msal.PublicClientApplication(msalConfig);

I både ADAL.js och MSAL.js är utfärdar-URI:n standard om https://login.microsoftonline.com/common du inte anger den.

Kommentar

Om du använder utfärdaren https://login.microsoftonline.com/common i v2.0 tillåter du användare att logga in med någon Microsoft Entra-organisation eller ett personligt Microsoft-konto (MSA). I MSAL.js använder du i stället om du vill begränsa inloggningen till ett Microsoft Entra-konto (samma beteende som med ADAL.js https://login.microsoftonline.com/organizations ).

Konfigurera MSAL

Några av konfigurationsalternativen i ADAL.js som används vid initiering av AuthenticationContext är inaktuella i MSAL.js, medan några nya introduceras. Se den fullständiga listan över tillgängliga alternativ. Det är viktigt att många av dessa alternativ, förutom clientId, kan åsidosättas under tokenförvärvet, så att du kan ange dem per begäran . Du kan till exempel använda en annan utfärdar-URI eller omdirigerings-URI än den du angav under initieringen när du hämtar token.

Dessutom behöver du inte längre ange inloggningsupplevelsen (det vill: om du använder popup-fönster eller omdirigerar sidan) via konfigurationsalternativen. MSAL.js Exponerar loginPopup i stället och loginRedirect metoder via instansenPublicClientApplication.

Aktivera loggning

I ADAL.js konfigurerar du loggning separat på valfri plats i koden:

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)

I MSAL.js är loggning en del av konfigurationsalternativen och skapas under initieringen av 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);

Växla till MSAL API

Några av de offentliga metoderna i ADAL.js har motsvarigheter i MSAL.js:

ADAL MSAL Kommentar
acquireToken acquireTokenSilent Har bytt namn och förväntar sig nu ett kontoobjekt
acquireTokenPopup acquireTokenPopup Asynkronisera nu och returnerar ett löfte
acquireTokenRedirect acquireTokenRedirect Asynkronisera nu och returnerar ett löfte
handleWindowCallback handleRedirectPromise Behövs om du använder omdirigeringsmiljön
getCachedUser getAllAccounts Har bytt namn och returnerar nu en matris med konton.

Andra var inaktuella, medan MSAL.js erbjuder nya metoder:

ADAL MSAL Kommentar
login Ej tillämpligt Inaktuell. Använd loginPopup eller loginRedirect
logOut Ej tillämpligt Inaktuell. Använd logoutPopup eller logoutRedirect
Saknas loginPopup
Saknas loginRedirect
Saknas logoutPopup
Saknas logoutRedirect
Saknas getAccountByHomeId Filtrerar konton efter hem-ID (oid + klient-ID)
Ej tillämpligt getAccountLocalId Filtrerar konton efter lokalt ID (användbart för ADFS)
Ej tillämpligt getAccountUsername Filtrerar konton efter användarnamn (om det finns)

Eftersom MSAL.js implementeras i TypeScript till skillnad från ADAL.js exponeras dessutom olika typer och gränssnitt som du kan använda i dina projekt. Mer information finns i API-referensen för MSAL.js.

Använda omfång i stället för resurser

En viktig skillnad mellan Azure Active Directory v1.0 och 2.0-slutpunkter handlar om hur resurserna används. När du använder ADAL.js med v1.0-slutpunkten registrerar du först en behörighet på appregistreringsportalen och begär sedan en åtkomsttoken för en resurs (till exempel Microsoft Graph) enligt nedan:

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

MSAL.js stöder endast v2.0-slutpunkten . V2.0-slutpunkten använder en omfångscentrerad modell för att få åtkomst till resurser. När du begär en åtkomsttoken för en resurs måste du därför också ange omfånget för resursen:

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

En fördel med den omfångscentrerade modellen är möjligheten att använda dynamiska omfång. När du skapar program med v1.0-slutpunkten behövde du registrera den fullständiga uppsättning behörigheter (så kallade statiska omfång) som krävs av programmet för att användaren ska kunna samtycka till vid tidpunkten för inloggningen. I v2.0 kan du använda omfångsparametern för att begära behörigheterna när du vill ha dem (därav dynamiska omfång). På så sätt kan användaren ge inkrementellt medgivande till omfång. Så om du i början bara vill att användaren ska logga in på ditt program och du inte behöver någon typ av åtkomst, kan du göra det. Om du senare behöver möjlighet att läsa användarens kalender kan du sedan begära kalenderomfånget i metoderna acquireToken och få användarens medgivande. Mer information finns i: Resurser och omfång

Använd löften i stället för återanrop

I ADAL.js används återanrop för alla åtgärder när autentiseringen har slutförts och ett svar erhålls:

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

I MSAL.js används löften i stället:

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

Du kan också använda syntaxen async/await som medföljer ES8:

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

Cachelagrar och hämtar token

Precis som ADAL.js cachelagrar MSAL.js tokens och andra autentiseringsartefakter i webbläsarlagring med hjälp av Webblagrings-API:et. Du rekommenderas att använda sessionStorage alternativet (se: konfiguration) eftersom det är säkrare att lagra token som hämtas av dina användare, men localStorage ger dig Enkel inloggning över flikar och användarsessioner.

Det är viktigt att du inte ska komma åt cachen direkt. I stället bör du använda ett lämpligt MSAL.js-API för att hämta autentiseringsartefakter som åtkomsttoken eller användarkonton.

Förnya token med uppdateringstoken

ADAL.js använder det implicita OAuth 2.0-flödet, som inte returnerar uppdateringstoken av säkerhetsskäl (uppdateringstoken har längre livslängd än åtkomsttoken och är därför farligare för skadliga aktörer). Därför utför ADAL.js tokenförnyelse med en dold IFrame så att användaren inte uppmanas att autentisera upprepade gånger.

Med autentiseringskodflödet med PKCE-stöd hämtar appar som använder MSAL.js 2.x uppdateringstoken tillsammans med ID och åtkomsttoken, som kan användas för att förnya dem. Användningen av uppdateringstoken abstraheras bort och utvecklarna ska inte skapa logik runt dem. I stället hanterar MSAL tokenförnyelse med hjälp av uppdateringstoken själv. Din tidigare tokencache med ADAL.js kan inte överföras till MSAL.js eftersom schemat för tokencache har ändrats och inte är kompatibelt med schemat som används i ADAL.js.

Hantera fel och undantag

När du använder MSAL.js är den vanligaste typen av fel som du kan stöta på interaction_in_progress felet. Det här felet utlöses när ett interaktivt API (loginPopup, , loginRedirectacquireTokenPopup, acquireTokenRedirect) anropas medan ett annat interaktivt API fortfarande pågår. login* API:erna och acquireToken* är asynkrona, så du måste se till att de resulterande löftena har lösts innan du anropar en annan.

Ett annat vanligt fel är interaction_required. Det här felet löses ofta genom att en interaktiv tokeninsamlingsprompt initieras. Webb-API:et som du försöker komma åt kan till exempel ha en princip för villkorsstyrd åtkomst som kräver att användaren utför multifaktorautentisering (MFA). I så fall kan du hantera interaction_required fel genom att acquireTokenPopup utlösa eller acquireTokenRedirect uppmana användaren att ange MFA, så att de kan fyll i det.

Ett annat vanligt fel som du kan stöta på är consent_required, som inträffar när behörigheter som krävs för att hämta en åtkomsttoken för en skyddad resurs inte godkänns av användaren. Precis som i interaction_requiredinitierar lösningen för consent_required felet ofta en interaktiv tokeninsamlingsprompt med hjälp av antingen acquireTokenPopup eller acquireTokenRedirect.

Mer information finns i Vanliga MSAL.js-fel och hur du hanterar dem

Använda HÄNDELSE-API:et

MSAL.js (>=v2.4) introducerar ett händelse-API som du kan använda i dina appar. Dessa händelser är relaterade till autentiseringsprocessen och vad MSAL gör när som helst och kan användas för att uppdatera användargränssnittet, visa felmeddelanden, kontrollera om någon interaktion pågår och så vidare. Nedan visas till exempel ett händelseåteranrop som anropas när inloggningsprocessen misslyckas av någon anledning:

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

För prestanda är det viktigt att avregistrera händelseåteranrop när de inte längre behövs. Mer information finns i : MSAL.js Events API

Hantera flera konton

ADAL.js har begreppet användare som representerar den autentiserade entiteten. MSAL.js ersätter användare med konton, med tanke på att en användare kan ha fler än ett konto associerat med dem. Det innebär också att du nu måste kontrollera för flera konton och välja den som du vill arbeta med. Kodfragmentet nedan illustrerar den här processen:

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

Mer information finns i: Konton i MSAL.js

Använda omslutningsbiblioteken

Om du utvecklar för Angular- och React-ramverk kan du använda MSAL Angular v2respektive MSAL React. Dessa omslutningar exponerar samma offentliga API som MSAL.js samtidigt som de erbjuder ramverksspecifika metoder och komponenter som kan effektivisera autentiserings- och tokenanskaffningsprocesserna.

Kör appen

När ändringarna är klara kör du appen och testar ditt autentiseringsscenario:

npm start

Exempel: Skydda ett SPA med ADAL.js jämfört med MSAL.js

Kodfragmenten nedan visar den minimala kod som krävs för ett ensidesprogram som autentiserar användare med Microsofts identitetsplattform och hämtar en åtkomsttoken för Microsoft Graph med hjälp av först ADAL.js och sedan MSAL.js:

Använda ADAL.js Använda MSAL.js

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript" src="https://alcdn.msauth.net/lib/1.0.18/js/adal.min.js"></script>
</head>

<body>
    <div>
        <p id="welcomeMessage" style="visibility: hidden;"></p>
        <button id="loginButton">Login</button>
        <button id="logoutButton" style="visibility: hidden;">Logout</button>
        <button id="tokenButton" style="visibility: hidden;">Get Token</button>
    </div>
    <script>
        // DOM elements to work with
        var welcomeMessage = document.getElementById("welcomeMessage");
        var loginButton = document.getElementById("loginButton");
        var logoutButton = document.getElementById("logoutButton");
        var tokenButton = document.getElementById("tokenButton");

        // if user is logged in, update the UI
        function updateUI(user) {
            if (!user) {
                return;
            }

            welcomeMessage.innerHTML = 'Hello ' + user.profile.upn + '!';
            welcomeMessage.style.visibility = "visible";
            logoutButton.style.visibility = "visible";
            tokenButton.style.visibility = "visible";
            loginButton.style.visibility = "hidden";
        };

        // attach logger configuration to window
        window.Logging = {
            piiLoggingEnabled: false,
            level: 3,
            log: function (message) {
                console.log(message);
            }
        };

        // ADAL configuration
        var adalConfig = {
            instance: 'https://login.microsoftonline.com/',
            clientId: "ENTER_CLIENT_ID_HERE",
            tenant: "ENTER_TENANT_ID_HERE",
            redirectUri: "ENTER_REDIRECT_URI_HERE",
            cacheLocation: "sessionStorage",
            popUp: true,
            callback: function (errorDesc, token, error, tokenType) {
                if (error) {
                    console.log(error, errorDesc);
                } else {
                    updateUI(authContext.getCachedUser());
                }
            }
        };

        // instantiate ADAL client object
        var authContext = new AuthenticationContext(adalConfig);

        // handle redirect response or check for cached user
        if (authContext.isCallback(window.location.hash)) {
            authContext.handleWindowCallback();
        } else {
            updateUI(authContext.getCachedUser());
        }

        // attach event handlers to button clicks
        loginButton.addEventListener('click', function () {
            authContext.login();
        });

        logoutButton.addEventListener('click', function () {
            authContext.logOut();
        });

        tokenButton.addEventListener('click', () => {
            authContext.acquireToken(
                "https://graph.microsoft.com",
                function (errorDesc, token, error) {
                    if (error) {
                        console.log(error, errorDesc);

                        authContext.acquireTokenPopup(
                            "https://graph.microsoft.com",
                            null, // extraQueryParameters
                            null, // claims
                            function (errorDesc, token, error) {
                                if (error) {
                                    console.log(error, errorDesc);
                                } else {
                                    console.log(token);
                                }
                            }
                        );
                    } else {
                        console.log(token);
                    }
                }
            );
        });
    </script>
</body>

</html>


<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript" src="https://alcdn.msauth.net/browser/2.34.0/js/msal-browser.min.js"></script>
</head>

<body>
    <div>
        <p id="welcomeMessage" style="visibility: hidden;"></p>
        <button id="loginButton">Login</button>
        <button id="logoutButton" style="visibility: hidden;">Logout</button>
        <button id="tokenButton" style="visibility: hidden;">Get Token</button>
    </div>
    <script>
        // DOM elements to work with
        const welcomeMessage = document.getElementById("welcomeMessage");
        const loginButton = document.getElementById("loginButton");
        const logoutButton = document.getElementById("logoutButton");
        const tokenButton = document.getElementById("tokenButton");

        // if user is logged in, update the UI
        const updateUI = (account) => {
            if (!account) {
                return;
            }

            welcomeMessage.innerHTML = `Hello ${account.username}!`;
            welcomeMessage.style.visibility = "visible";
            logoutButton.style.visibility = "visible";
            tokenButton.style.visibility = "visible";
            loginButton.style.visibility = "hidden";
        };

        // MSAL configuration
        const msalConfig = {
            auth: {
                clientId: "ENTER_CLIENT_ID_HERE",
                authority: "https://login.microsoftonline.com/ENTER_TENANT_ID_HERE",
                redirectUri: "ENTER_REDIRECT_URI_HERE",
            },
            cache: {
                cacheLocation: "sessionStorage"
            },
            system: {
                loggerOptions: {
                    loggerCallback(loglevel, message, containsPii) {
                        console.log(message);
                    },
                    piiLoggingEnabled: false,
                    logLevel: msal.LogLevel.Verbose,
                }
            }
        };

        // instantiate MSAL client object
        const pca = new msal.PublicClientApplication(msalConfig);

        // handle redirect response or check for cached user
        pca.handleRedirectPromise().then((response) => {
            if (response) {
                pca.setActiveAccount(response.account);
                updateUI(response.account);
            } else {
                const account = pca.getAllAccounts()[0];
                updateUI(account);
            }
        }).catch((error) => {
            console.log(error);
        });

        // attach event handlers to button clicks
        loginButton.addEventListener('click', () => {
            pca.loginPopup().then((response) => {
                pca.setActiveAccount(response.account);
                updateUI(response.account);
            })
        });

        logoutButton.addEventListener('click', () => {
            pca.logoutPopup().then((response) => {
                window.location.reload();
            });
        });

        tokenButton.addEventListener('click', () => {
            const account = pca.getActiveAccount();

            pca.acquireTokenSilent({
                account: account,
                scopes: ["User.Read"]
            }).then((response) => {
                console.log(response);
            }).catch((error) => {
                if (error instanceof msal.InteractionRequiredAuthError) {
                    pca.acquireTokenPopup({
                        scopes: ["User.Read"]
                    }).then((response) => {
                        console.log(response);
                    });
                }

                console.log(error);
            });
        });
    </script>
</body>

</html>

Nästa steg