Migration d’une application Node.js d’ADAL vers MSAL

La bibliothèque d’authentification Microsoft pour Node (MSAL Node) est désormais le kit de développement logiciel (SDK) recommandé pour l’activation de l’authentification et de l’autorisation pour vos applications inscrites sur la plateforme d’identité Microsoft. Cet article décrit les étapes importantes à suivre pour migrer vos applications de la bibliothèque d’authentification Active Directory pour Node (ADAL Node) vers MSAL Node.

Prérequis

Mettre à jour les paramètres d’inscription d’application

Lorsque de votre travail sur ADAL Node, vous utilisiez probablement le point de terminaison Azure AD v1.0. Les applications qui migrent d’ADAL vers MSAL doivent également basculer vers le point de terminaison Azure AD v2.0.

Installer et importer des packages

  1. Installez le package MSAL Node via npm :
  npm install @azure/msal-node
  1. Après cela, importez MSAL Node dans votre code :
  const msal = require('@azure/msal-node');
  1. Enfin, désinstallez le package d’ADAL Node et supprimez toutes les références dans votre code :
  npm uninstall adal-node

Initialiser MSAL

Dans ADAL Node, initialisez un objet AuthenticationContext, qui expose ensuite les méthodes utilisables dans différents flux d’authentification (par exemple, acquireTokenWithAuthorizationCode pour les applications web). Lors de l’initialisation, le seul paramètre obligatoire est l’URI d’autorité :

var adal = require('adal-node');

var authorityURI = "https://login.microsoftonline.com/common";
var authenticationContex = new adal.AuthenticationContext(authorityURI);

Dans MSAL Node, vous avez deux possibilités : si vous créez une application mobile ou une application de bureau, vous instanciez un objet PublicClientApplication. Le constructeur attend un objet de configuration qui contient le paramètre clientId au minimum. MSAL définit par défaut l’URI d’autorité à https://login.microsoftonline.com/common si vous ne le spécifiez pas.

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

const pca = new msal.PublicClientApplication({
        auth: {
            clientId: "YOUR_CLIENT_ID"
        }
    });

Remarque

Si vous utilisez l’autorité https://login.microsoftonline.com/common dans v2.0, vous autorisez les utilisateurs à se connecter à n’importe quelle organisation Microsoft Entra ou à un compte personnel Microsoft (MSA). Dans MSAL Node, si vous voulez limiter la connexion à un compte Microsoft Entra (même comportement qu’avec ADAL Node), utilisez https://login.microsoftonline.com/organizations à la place.

Si vous créez une application web ou une application démon, vous instanciez un objet ConfidentialClientApplication. Avec ces applications, vous devez également fournir des informations d’identification de client, comme une clé secrète client ou un certificat :

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

const cca = new msal.ConfidentialClientApplication({
        auth: {
            clientId: "YOUR_CLIENT_ID",
            clientSecret: "YOUR_CLIENT_SECRET"
        }
    });

PublicClientApplication et ConfidentialClientApplication, contrairement AuthenticationContext d’ADAL, sont liés à un ID client. Cela signifie que si vous avez différents ID client que vous souhaitez utiliser dans votre application, vous devez instancier une nouvelle instance de MSAL pour chacun d’entre eux. Pour plus d’informations : Initialisation de MSAL Node

Configurer MSAL

Lors de la création d’applications sur la plateforme d’identité Microsoft, votre application contiendra de nombreux paramètres liés à l’authentification. Dans ADAL Node, l’objet AuthenticationContext a un nombre limité de paramètres de configuration avec lesquels vous pouvez l’instancier, tandis que les paramètres restants résident librement dans votre code (par exemple, clientSecret) :

var adal = require('adal-node');

var authority = "https://login.microsoftonline.com/YOUR_TENANT_ID"
var validateAuthority = true,
var cache = null;

var authenticationContext = new adal.AuthenticationContext(authority, validateAuthority, cache);
  • authority : une URL qui identifie une autorité de jeton
  • validateAuthority : une fonctionnalité qui empêche votre code de demander des jetons à une autorité potentiellement malveillante
  • cache : définit le cache de jeton utilisé par cette instance d’AuthenticationContext. Si ce paramètre n’est pas défini, une valeur par défaut est utilisée dans le cache en mémoire

En revanche, MSAL Node utilise un objet de configuration de type Configuration. Il contient les propriétés suivantes :

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

const msalConfig = {
    auth: {
        clientId: "YOUR_CLIENT_ID",
        authority: "https://login.microsoftonline.com/YOUR_TENANT_ID",
        clientSecret: "YOUR_CLIENT_SECRET",
        knownAuthorities: [],
    },
    cache: {
        // your implementation of caching
    },
    system: {
        loggerOptions: { /** logging related options */ }
    }
}


const cca = new msal.ConfidentialClientApplication(msalConfig);

Différence notable, MSAL n’a pas d’indicateur pour désactiver la validation de l’autorité, et les autorités sont toujours validées par défaut. MSAL compare votre autorité demandée à une liste d’autorités connues de Microsoft ou à la liste des autorités que vous avez spécifiées dans votre configuration. Pour plus d’informations : Options de configuration

Basculer vers l’API MSAL

La plupart des méthodes publiques d’ADAL Node ont des équivalents dans MSAL Node :

ADAL MSAL Notes
acquireToken acquireTokenSilent Renommé et attend désormais un objet account
acquireTokenWithAuthorizationCode acquireTokenByCode
acquireTokenWithClientCredentials acquireTokenByClientCredential
acquireTokenWithRefreshToken acquireTokenByRefreshToken Utile pour la migration de jetons d’actualisation valides
acquireTokenWithDeviceCode acquireTokenByDeviceCode Simplifie maintenant l’acquisition du code utilisateur (voir ci-dessous)
acquireTokenWithUsernamePassword acquireTokenByUsernamePassword

Toutefois, certaines méthodes d’ADAL Node sont dépréciées, tandis que MSAL Node en offre de nouvelles :

ADAL MSAL Notes
acquireUserCode N/A Fusionné avec acquireTokeByDeviceCode (voir ci-dessus)
N/A acquireTokenOnBehalfOf Nouvelle méthode qui simplifie le flux OBO
acquireTokenWithClientCertificate N/A N’est plus nécessaire, car les certificats sont attribués au cours de l’initialisation (voir options de configuration)
N/A getAuthCodeUrl Nouvelle méthode qui simplifie la construction d’une URL de point de terminaison d’autorisation

Utiliser des étendues au lieu de ressources

L’accès aux ressources est une différence importante entre les points de terminaison v1.0 et v2.0. Dans ADAL Node, vous devez d’abord inscrire une autorisation sur le portail d’inscription des applications, puis demander un jeton d’accès pour une ressource (par exemple, Microsoft Graph) comme indiqué ci-dessous :

authenticationContext.acquireTokenWithAuthorizationCode(
    req.query.code,
    redirectUri,
    resource, // e.g. 'https://graph.microsoft.com'
    clientId,
    clientSecret,
    function (err, response) {
        // do something with the authentication response
    }
);

MSAL Node 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 :

const tokenRequest = {
    code: req.query.code,
    scopes: ["https://graph.microsoft.com/User.Read"],
    redirectUri: REDIRECT_URI,
};

pca.acquireTokenByCode(tokenRequest).then((response) => {
    // do something with the authentication response
}).catch((error) => {
    console.log(error);
});

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 Node, les rappels sont utilisés pour toute opération une fois que l’authentification a réussi et qu’une réponse est obtenue :

var context = new AuthenticationContext(authorityUrl, validateAuthority);

context.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, response) {
    if (err) {
        console.log(err);
    } else {
        // do something with the authentication response
    }
});

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

    const cca = new msal.ConfidentialClientApplication(msalConfig);

    cca.acquireTokenByClientCredential(tokenRequest).then((response) => {
        // do something with the authentication response
    }).catch((error) => {
        console.log(error);
    });

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

    try {
        const authResponse = await cca.acquireTokenByCode(tokenRequest);
    } catch (error) {
        console.log(error);
    }

Activation de la journalisation

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

var adal = require('adal-node');

//PII or OII logging disabled. Default Logger does not capture any PII or OII.
adal.logging.setLoggingOptions({
  log: function (level, message, error) {
    console.log(message);

    if (error) {
        console.log(error);
    }
  },
  level: logging.LOGGING_LEVEL.VERBOSE, // provide the logging level
  loggingWithPII: false  // Determine if you want to log personal identification information. The default value is false.
});

Dans MSAL Node, la journalisation fait partie des options de configuration et est créée avec l’initialisation de l’instance MSAL Node :

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

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 cca = new msal.ConfidentialClientApplication(msalConfig);

Activer la mise en cache des jetons

Dans ADAL Node, vous aviez la possibilité d’importer un cache de jeton en mémoire. Le cache de jeton est utilisé en tant que paramètre lors de l’initialisation d’un objet AuthenticationContext :

var MemoryCache = require('adal-node/lib/memory-cache');

var cache = new MemoryCache();
var authorityURI = "https://login.microsoftonline.com/common";

var context = new AuthenticationContext(authorityURI, true, cache);

MSAL Node utilise un cache de jeton en mémoire par défaut. Vous n’avez pas besoin de l’importer explicitement : le cache de jetons en mémoire est exposé avec les classes ConfidentialClientApplication et PublicClientApplication.

const msalTokenCache = publicClientApplication.getTokenCache();

Il est important de noter que votre cache de jetons précédent avec ADAL Node n’est pas transférable vers MSAL Node en raison de l’incompatibilité des schémas de cache. En revanche, vous pouvez utiliser les jetons d’actualisation valides que votre application a obtenus précédemment avec ADAL Node dans MSAL Node. Pour en savoir plus, consultez la section Jetons d’actualisation.

Vous pouvez également écrire votre cache sur disque en fournissant votre propre plug-in de cache. Le plug-in du cache doit implémenter l’interface ICachePlugin. Comme la journalisation, la mise en cache fait partie des options de configuration et est créée avec l’initialisation de l’instance MSAL Node :

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

const msalConfig = {
    auth: {
        // authentication related parameters
    },
    cache: {
        cachePlugin // your implementation of cache plugin
    },
    system: {
        // logging related options
    }
}

const msalInstance = new ConfidentialClientApplication(msalConfig);

Un exemple de plug-in de cache peut être implémenté comme indiqué ci-dessous :

const fs = require('fs');

// Call back APIs which automatically write and read into a .json file - example implementation
const beforeCacheAccess = async (cacheContext) => {
    cacheContext.tokenCache.deserialize(await fs.readFile(cachePath, "utf-8"));
};

const afterCacheAccess = async (cacheContext) => {
    if(cacheContext.cacheHasChanged) {
        await fs.writeFile(cachePath, cacheContext.tokenCache.serialize());
    }
};

// Cache Plugin
const cachePlugin = {
    beforeCacheAccess,
    afterCacheAccess
};

Si vous développez des applications clientes publiques, comme des applications de bureau, les extensions d’authentification Microsoft pour Node offrent des mécanismes sécurisés permettant aux applications clientes d’effectuer la sérialisation et la persistance du cache de jetons multiplateforme. Les plateformes prises en charge sont Windows, Mac et Linux.

Notes

Les extensions d’authentification Microsoft pour Node ne sont pas recommandées pour les applications web, car cela peut entraîner des problèmes de mise à l’échelle et de performances. Au lieu de cela, les applications web sont recommandées pour rendre le cache persistant dans la session.

Suppression de la logique entourant les jetons d’actualisation

Dans ADAL Node, les jetons d’actualisation (RT) ont été exposés, ce qui vous permet de développer des solutions autour de l’utilisation de ces jetons en les mettant en cache et en utilisant la méthode acquireTokenWithRefreshToken. Scénarios classiques dans lesquels les RT sont particulièrement pertinents :

  • Services durables qui exécutent des actions, notamment l’actualisation des tableaux de bord pour le compte des utilisateurs alors que ces utilisateurs ne sont plus connectés.
  • Scénarios WebFarm pour permettre au client d’apporter le RT au service web (la mise en cache est effectuée côté client, le cookie est chiffré, et non côté serveur).

Pour des raisons de sécurité, MSAL Node, comme d’autres MSAL, n’expose pas les jetons d’actualisation. C’est MSAL qui gère l’actualisation des jetons pour vous. Par conséquent, vous n’avez plus besoin de générer une logique pour cela. Toutefois, vous pouvez utiliser vos jetons d’actualisation précédemment acquis (et encore valides) à partir du cache d’ADAL Node pour obtenir un nouvel ensemble de jetons avec MSAL Node. Pour ce faire, MSAL Node offre acquireTokenByRefreshToken, qui équivaut à la méthode acquireTokenWithRefreshToken d’ADAL Node :

var msal = require('@azure/msal-node');

const config = {
    auth: {
        clientId: "ENTER_CLIENT_ID",
        authority: "https://login.microsoftonline.com/ENTER_TENANT_ID",
        clientSecret: "ENTER_CLIENT_SECRET"
    }
};

const cca = new msal.ConfidentialClientApplication(config);

const refreshTokenRequest = {
    refreshToken: "", // your previous refresh token here
    scopes: ["https://graph.microsoft.com/.default"],
    forceCache: true,
};

cca.acquireTokenByRefreshToken(refreshTokenRequest).then((response) => {
    console.log(response);
}).catch((error) => {
    console.log(error);
});

Pour plus d’informations, reportez-vous à l’exemple de migration de nœud ADAL vers un nœud MSAL.

Notes

Nous vous recommandons de détruire l’ancien cache de jeton d’ADAL Node une fois que vous avez utilisé les jetons d’actualisation toujours valides pour obtenir un nouvel ensemble de jetons à l’aide de la méthode acquireTokenByRefreshToken de MSAL Node, comme indiqué ci-dessus.

Gérer les erreurs et exceptions

Lorsque vous utilisez MSAL Node, le type d’erreur le plus courant que vous pouvez rencontrer est l’erreur interaction_required. Cette erreur est souvent résolue en lançant une invite d’acquisition de jeton interactive. Par exemple, quand vous utilisez acquireTokenSilent, si aucun jeton d’actualisation n’est mis en cache, MSAL Node ne peut pas acquérir un jeton d’accès en mode silencieux. De la même façon, 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 de tels cas, traiter l’erreur interaction_required en déclenchant acquireTokenByCode a pour effet d’inviter l’utilisateur à effectuer 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 invite d’acquisition de jetons interactive, en utilisant la méthode acquireTokenByCode.

Exécuter l’application

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

npm start

Exemple : Acquisition de jetons avec ADAL Node ou MSAL Node

L’extrait de code ci-dessous illustre une application web cliente confidentielle dans le framework Express.js. Elle effectue une connexion lorsqu’un utilisateur accède à l’itinéraire d’authentification /auth, acquiert un jeton d’accès pour Microsoft Graph via l’itinéraire /redirect, puis affiche le contenu de ce jeton.

Utiliser le nœud ADAL Utiliser le nœud MSAL
// Import dependencies
var express = require('express');
var crypto = require('crypto');
var adal = require('adal-node');

// Authentication parameters
var clientId = 'Enter_the_Application_Id_Here';
var clientSecret = 'Enter_the_Client_Secret_Here';
var tenant = 'Enter_the_Tenant_Info_Here';
var authorityUrl = 'https://login.microsoftonline.com/' + tenant;
var redirectUri = 'http://localhost:3000/redirect';
var resource = 'https://graph.microsoft.com';

// Configure logging
adal.Logging.setLoggingOptions({
    log: function (level, message, error) {
        console.log(message);
    },
    level: adal.Logging.LOGGING_LEVEL.VERBOSE,
    loggingWithPII: false
});

// Auth code request URL template
var templateAuthzUrl = 'https://login.microsoftonline.com/'
    + tenant + '/oauth2/authorize?response_type=code&client_id='
    + clientId + '&redirect_uri=' + redirectUri
    + '&state=<state>&resource=' + resource;

// Initialize express
var app = express();

// State variable persists throughout the app lifetime
app.locals.state = "";

app.get('/auth', function(req, res) {

    // Create a random string to use against XSRF
    crypto.randomBytes(48, function(ex, buf) {
        app.locals.state = buf.toString('base64')
            .replace(/\//g, '_')
            .replace(/\+/g, '-');

        // Construct auth code request URL
        var authorizationUrl = templateAuthzUrl
            .replace('<state>', app.locals.state);

        res.redirect(authorizationUrl);
    });
});

app.get('/redirect', function(req, res) {
    // Compare state parameter against XSRF
    if (app.locals.state !== req.query.state) {
        res.send('error: state does not match');
    }

    // Initialize an AuthenticationContext object
    var authenticationContext =
        new adal.AuthenticationContext(authorityUrl);

    // Exchange auth code for tokens
    authenticationContext.acquireTokenWithAuthorizationCode(
        req.query.code,
        redirectUri,
        resource,
        clientId,
        clientSecret,
        function(err, response) {
            res.send(response);
        }
    );
});

app.listen(3000, function() {
    console.log(`listening on port 3000!`);
});
// Import dependencies
const express = require("express");
const msal = require('@azure/msal-node');

// Authentication parameters
const config = {
    auth: {
        clientId: "Enter_the_Application_Id_Here",
        authority: "https://login.microsoftonline.com/Enter_the_Tenant_Info_Here",
        clientSecret: "Enter_the_Client_Secret_Here"
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: msal.LogLevel.Verbose,
        }
    }
};

const REDIRECT_URI = "http://localhost:3000/redirect";

// Initialize MSAL Node object using authentication parameters
const cca = new msal.ConfidentialClientApplication(config);

// Initialize express
const app = express();

app.get('/auth', (req, res) => {

    // Construct a request object for auth code
    const authCodeUrlParameters = {
        scopes: ["user.read"],
        redirectUri: REDIRECT_URI,
    };

    // Request auth code, then redirect
    cca.getAuthCodeUrl(authCodeUrlParameters)
        .then((response) => {
            res.redirect(response);
        }).catch((error) => res.send(error));
});

app.get('/redirect', (req, res) => {

    // Use the auth code in redirect request to construct
    // a token request object
    const tokenRequest = {
        code: req.query.code,
        scopes: ["user.read"],
        redirectUri: REDIRECT_URI,
    };

    // Exchange the auth code for tokens
    cca.acquireTokenByCode(tokenRequest)
        .then((response) => {
            res.send(response);
        }).catch((error) => res.status(500).send(error));
});

app.listen(3000, () =>
    console.log(`listening on port 3000!`));

Étapes suivantes