Comment migrer une application JavaScript d'ADAL.js vers MSAL.js

La Bibliothèque d’authentification Microsoft pour JavaScript (MSAL.js, également appelée msal-browser) 2.x est la bibliothèque d’authentification que nous recommandons d’utiliser avec les applications JavaScript sur la plateforme d’identité Microsoft. Cet article met en évidence les modifications que vous devez effectuer pour migrer une application qui utilise ADAL.js pour utiliser MSAL.js 2. x

Notes

Nous vous recommandons vivement MSAL.js 2. x plutôt que MSAL.js 1. x. Le processus d’octroi de code d’authentification est plus sécurisé et permet aux applications à page unique de conserver une bonne expérience utilisateur malgré les mesures de confidentialité que les navigateurs tels que Safari ont implémentées pour bloquer les cookies tiers, entre autres avantages.

Configuration requise

  • Vous devez définir le Type d’URL de réponse / de la Plateforme sur Application monopage dans le portail Inscription d’application. (Si vous avez d’autres plateformes ajoutées dans votre inscription d’application, comme Web, vous devez veiller à ce que les URI de redirection ne se chevauchent pas. Consultez Restrictions applicables aux URI de redirection.)
  • Vous devez fournir des polyremplissages pour les fonctionnalités ES6 sur lesquelles s’appuie MSAL.js (par exemple, les promesses) afin d’exécuter vos applications sur Internet Explorer.
  • Migrez vos applications Microsoft Entra vers le point de terminaison v2 si vous ne l'avez pas déjà fait

Installer et importer des packages

Il existe deux façons d’installer la bibliothèque MSAL.js 2. x :

Via npm :

npm install @azure/msal-browser

Ensuite, en fonction de votre système de module, importez-la comme indiqué ci-dessous :

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

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

Via CDN :

Chargez le script dans la section d’en-tête de votre document 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>

Pour obtenir d’autres liens CDN et les meilleures pratiques lors de l’utilisation de CDN, consultez : utilisation de CDN

Initialiser MSAL

Dans ADAL.js, vous instanciez la classe AuthenticationContext qui expose ensuite les méthodes qui permettent l’authentification (login, acquireTokenPopup etc.). Cet objet sert de représentation de la connexion de votre application au serveur d’autorisation ou au fournisseur d’identité. Lors de l’initialisation, le seul paramètre obligatoire est leclientId :

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

var authContext = new AuthenticationContext(config);

IDans ADAL.js, vous instanciez la classe PublicClientApplication à la place. A l’instar d’ADAL.js, le constructeur attend un objet de configuration qui contient le clientId paramètre au minimum. En savoir plus : Initialiser MSAL.js

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

const msalInstance = new msal.PublicClientApplication(msalConfig);

Dans ADAL.js et MSAL.js, l’URI d’autorité est défini par défaut sur https://login.microsoftonline.com/common si vous ne le spécifiez pas.

Remarque

Si vous utilisez l'autorité https://login.microsoftonline.com/common de la version 2.0, vous autoriserez les utilisateurs à se connecter avec n'importe quelle organisation Microsoft Entra ou avec un compte Microsoft personnel (MSA). Dans MSAL.js, si vous souhaitez restreindre la connexion à n'importe quel compte Microsoft Entra (même comportement qu'avec ADAL.js), utilisez-le https://login.microsoftonline.com/organizations à la place.

Configurer MSAL

Certaines options de configuration dans ADAL.js utilisées lors de l’initialisation du AuthenticationContext sont dépréciées dans MSAL.js, bien que de nouvelles y soient introduites. Consultez la liste complète des options disponibles. Surtout, la plupart de ces options, à l’exception de clientId, peuvent être remplacées lors de l’acquisition de jetons, ce qui vous permet de les définir en fonction de la demande. Par exemple, vous pouvez utiliser une URI d’autorité ou URI de redirection différente de celle que vous avez définie à l’initialisation lors de l’acquisition de jetons.

En outre, vous n’avez plus besoin de spécifier l’expérience de connexion (par exemple, si vous utilisez des fenêtres contextuelles ou si vous redirigez la page) via les options de configuration. A la place, MSAL.js expose les méthodes loginPopup et loginRedirect dans l’instance PublicClientApplication.

Activation de la journalisation

Dans ADAL.js, vous configurez la journalisation séparément à n’importe quel emplacement dans votre code :

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)

Dans MSAL.js, la journalisation fait partie des options de configuration et est créée pendant l’initialisation de 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);

Basculer vers l’API MSAL

La plupart des méthodes publiques dans ADAL.js ont des équivalents dans MSAL.js :

ADAL MSAL Notes
acquireToken acquireTokenSilent Renommé et attend désormais un objet account
acquireTokenPopup acquireTokenPopup Maintenant asynchrone et retourne une promesse
acquireTokenRedirect acquireTokenRedirect Maintenant asynchrone et retourne une promesse
handleWindowCallback handleRedirectPromise Requise en cas d’utilisation de l’expérience de redirection
getCachedUser getAllAccounts Renommée et retourne maintenant un tableau de comptes.

D’autres ont été dépréciées, tandis que MSAL.js offre de nouvelles méthodes :

ADAL MSAL Notes
login N/A Action déconseillée. Utilisez loginPopup ou loginRedirect
logOut N/A Action déconseillée. Utilisez logoutPopup ou logoutRedirect
N/A loginPopup
N/A loginRedirect
N/A logoutPopup
N/A logoutRedirect
N/A getAccountByHomeId Filtre les comptes par ID de famille (OID + ID de locataire)
N/A getAccountLocalId Filtre les comptes par ID local (utile pour ADFS)
N/A getAccountUsername Filtre les comptes par nom d’utilisateur (s’il existe)

En outre, comme MSAL.js est implémentée dans TypeScript contrairement à ADAL.js, elle expose différents types et interfaces que vous pouvez utiliser dans vos projets. Pour plus d’informations, consultez les informations de référence sur l’API MSAL.js.

Utiliser des étendues au lieu de ressources

Le moyen d’accès aux ressources est une différence majeure entre les points de terminaison Azure AD v1.0 et v2.0. Lors de l’utilisation d’ADAL.js avec le point de terminaison v1.0, vous devez d’abord inscrire une autorisation sur le portail d’inscription d’application, puis demander un jeton d’accès pour une ressource (par exemple, Microsoft Graph) comme indiqué ci-dessous :

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

MSAL.js prend uniquement en charge le point de terminaison v2.0. Le point de terminaison v2.0 utilise un modèle centré sur l’étendue pour accéder aux ressources. Ainsi, lorsque vous demandez un jeton d’accès pour une ressource, vous devez également spécifier l’étendue de cette ressource :

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

L’un des avantages du modèle centré sur l’étendue est la possibilité d’utiliser des étendues dynamiques. Lorsque vous génériez des applications à l'aide de la version 1.0, vous deviez enregistrer le jeu complet d'autorisations (appelées étendues statiques) exigé par l'application pour que l'utilisateur donne son consentement au moment de la connexion. Dans la version 2.0, vous pouvez utiliser le paramètre scope pour demander les autorisations au moment où vous le souhaitez (d’où le terme d’étendues dynamiques). Cela permet à l'utilisateur de fournir un consentement incrémentiel aux étendues. Par conséquent, si au début vous souhaitez juste que l'utilisateur se connecte à votre application et que vous n'avez besoin d'aucun type d'accès, c'est possible. Si, par la suite, vous devez avoir la possibilité de lire le calendrier de l'utilisateur, vous pouvez alors demander l'étendue de celui-ci dans les méthodes acquireToken et obtenir le consentement de l'utilisateur. Pour plus d’informations, consultez Ressources et étendues

Utilisation de promesses au lieu de rappels

Dans ADAL.js, les rappels sont utilisés pour toute opération une fois que l’authentification a réussi et qu’une réponse est obtenue :

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

Dans MSAL.js, des promesses sont utilisées à la place :

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

Vous pouvez également utiliser la syntaxe async/await qui est fournie avec ES8 :

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

Mise en cache et récupération de jetons

Comme ADAL.js, MSAL.js met en cache les jetons et autres artefacts d’authentification dans le stockage du navigateur, à l’aide de l'API de Stockage Web. Nous vous conseillons d’utiliser l’option sessionStorage (consultez configuration) car elle sécurise davantage le stockage des jetons obtenus par vos utilisateurs. Cependant, localStorage vous accorde l’Authentification unique sur les onglets et sessions utilisateur.

Important : vous n’êtes pas censé accéder au cache directement. Au lieu de cela, vous devez utiliser une API de MSAL.js appropriée pour récupérer les artefacts d’authentification, comme les jetons d’accès ou les comptes d’utilisateur.

Renouveler des jetons avec des jetons d’actualisation

ADAL.js utilise le flux implicite OAuth 2.0, lequel ne retourne pas les jetons d’actualisation pour des raisons de sécurité (les jetons d’actualisation ont une durée de vie plus longue que les jetons d’accès et sont donc plus dangereux quand ils sont entre les mains d’acteurs malveillants). Par conséquent, ADAL.js renouvelle les jetons au moyen d’un IFrame masqué afin que l’utilisateur ne soit pas invité à s’authentifier à plusieurs reprises.

Grâce au flux de code d’authentification avec prise en charge de PKCE, les applications qui utilisent MSAL.js 2. x obtiennent des jetons d’actualisation, ainsi que des ID et des jetons d’accès, qui peuvent être utilisés pour leur renouvellement. L’utilisation de jetons d’actualisation est rendue abstraite, et les développeurs ne sont pas censés créer une logique autour d’eux. Au lieu de cela, MSAL gère indépendamment le renouvellement des jetons à l’aide de jetons d’actualisation. Votre cache de jetons précédent avec ADAL.js ne pourra pas être transféré vers MSAL.js, car le schéma du cache de jetons a changé et est incompatible avec le schéma utilisé dans ADAL.js.

Gérer les erreurs et exceptions

Lorsque vous utilisez MSAL.js, le type d’erreur le plus courant que vous pouvez rencontrer est l'erreurinteraction_in_progress. Cette erreur est générée lorsqu’une API interactive (loginPopup, loginRedirect, acquireTokenPopup, acquireTokenRedirect) est appelée alors qu’une autre API interactive est toujours en cours. Les API login* et acquireToken* sont asynchrones ; vous devrez donc vous assurer de la résolution des promesses obtenues avant d’en invoquer une autre.

Une autre erreur courante est interaction_required. Cette erreur est souvent résolue en lançant une invite d’acquisition de jeton interactive. Par exemple, l’API web à laquelle vous tentez d’accéder peut appliquer une stratégie d’accès conditionnel, nécessitant que l’utilisateur effectue une authentification multifacteur (MFA). Dans ce cas, traiter l’interaction_required erreur en déclenchant acquireTokenPopup ou acquireTokenRedirect invitera l’utilisateur à réaliser une MFA, en lui permettant de la compléter.

Une autre erreur courante que vous pouvez rencontrer est l’erreur consent_required, qui se produit lorsque les autorisations requises pour obtenir un jeton d’accès pour une ressource protégée ne sont pas consenties par l’utilisateur. Comme pour interaction_required, la solution pour l’erreur consent_required est souvent d’initier une demande d’acquisition de jetons interactive, en utilisant soit acquireTokenPopup soit acquireTokenRedirect.

En savoir plus : erreurs MSAL.js courantes et comment les résoudre

Utiliser l’API d’événements

MSAL.js (>=v2.4) introduit une API d’événements que vous pouvez utiliser dans vos applications. Ces événements sont liés au processus d’authentification et à ce que MSAL fait à tout moment et peuvent être utilisés pour mettre à jour l’interface utilisateur, afficher les messages d’erreur, vérifier si une interaction est en cours, et ainsi de suite. Par exemple, voici un rappel d’événement qui sera appelé lorsque le processus de connexion échoue pour une raison quelconque :

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

Pour les performances, il est important de désinscrire les rappels d’événements devenus inutiles. En savoir plus : API d’événements MSAL.js

Gestion de plusieurs comptes

ADAL.js détient le concept utilisateur pour représenter l’entité authentifiée actuelle. MSAL.js remplace les utilisateurs avec plusieurs comptes, du fait qu’un utilisateur peut être associé à plus d’un compte. Cela signifie également que vous devez maintenant contrôler plusieurs comptes et choisir celui qui convient. L’extrait de code ci-dessous illustre ce processus :

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

Pour plus d’informations, consultez Comptes dans MSAL.js

Utilisation des bibliothèques wrappers

Si vous développez des infrastructures Angular et React, vous pouvez utiliser respectivement MSAL Angular v2 et MSAL React. Ces wrappers exposent la même API publique que MSAL.js tout en offrant des méthodes et des composants spécifiques à l’infrastructure qui peuvent simplifier les processus d’authentification et d’acquisition de jetons.

Exécuter l’application

Une fois vos modifications effectuées, exécutez l’application et testez votre scénario d’authentification :

npm start

Exemple : Sécurisation d’une application monopage avec ADAL.js et MSAL.js

Les extraits de code ci-dessous illustrent le code minimal requis pour une application à page unique qui authentifie les utilisateurs grâce à la plateforme d’identités Microsoft et obtient un jeton d’accès pour Microsoft Graph en utilisant d’abord ADAL.js puis MSAL.js :

Utilisation d’ADAL.js Utilisation de 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>

Étapes suivantes