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

Microsoft Authentication Library per Node (nodo MSAL) è ora l'SDK consigliato per abilitare l'autenticazione e l'autorizzazione per le applicazioni registrate in Microsoft Identity Platform. Questo articolo illustra i passaggi importanti da eseguire per eseguire la migrazione delle app da Active Directory Authentication Library per node (nodo ADAL) al nodo MSAL.

Prerequisiti

  • Nodo versione 10, 12, 14, 16 o 18. Vedere la nota sul supporto della versione

Aggiornare le impostazioni di registrazione dell'app

Quando si usa ad ADAL Node, è probabile che si usi l'endpoint di Azure AD v1.0. Le app che eseguono la migrazione da ADAL a MSAL devono passare all'endpoint di Azure AD v2.0.

Installare e importare MSAL

  1. installare il pacchetto node MSAL tramite npm:
  npm install @azure/msal-node
  1. Successivamente, importare il nodo MSAL nel codice:
  const msal = require('@azure/msal-node');
  1. Infine, disinstallare il pacchetto ADAL Node e rimuovere eventuali riferimenti nel codice:
  npm uninstall adal-node

Inizializzare MSAL

Nel nodo ADAL si inizializza un AuthenticationContext oggetto che espone quindi i metodi che è possibile usare in flussi di autenticazione diversi, acquireTokenWithAuthorizationCode ad esempio per le app Web. Durante l'inizializzazione, l'unico parametro obbligatorio è l'URI dell'autorità:

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

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

Nel nodo MSAL sono invece disponibili due alternative: se si sta creando un'app per dispositivi mobili o un'app desktop, si crea un'istanza di un PublicClientApplication oggetto . Il costruttore prevede un oggetto di configurazione che contiene almeno il clientId parametro . MSAL imposta per impostazione predefinita l'URI https://login.microsoftonline.com/common dell'autorità su se non viene specificato.

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

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

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 . Nel nodo MSAL, se si vuole limitare l'accesso a qualsiasi account Microsoft Entra (stesso comportamento di quello di ADAL Node), usare https://login.microsoftonline.com/organizations invece .

D'altra parte, se si sta creando un'app Web o un'app daemon, si crea un'istanza di un ConfidentialClientApplication oggetto . Con tali app è anche necessario fornire credenziali client, ad esempio un segreto client o un certificato:

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

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

Sia PublicClientApplication che ConfidentialClientApplication, a differenza di AuthenticationContextADAL, è associato a un ID client. Ciò significa che se si dispone di ID client diversi che si desidera usare nell'applicazione, è necessario creare un'istanza di MSAL nuova per ogni istanza. Per altre informazioni, vedere Inizializzazione del nodo MSAL

Configurare MSAL

Quando si creano app in Microsoft Identity Platform, l'app conterrà molti parametri correlati all'autenticazione. Nel nodo ADAL l'oggetto AuthenticationContext ha un numero limitato di parametri di configurazione con cui è possibile crearne un'istanza, mentre i parametri rimanenti si bloccano liberamente nel codice, ad esempio 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: URL che identifica un'autorità di token
  • validateAuthority: funzionalità che impedisce al codice di richiedere token da un'autorità potenzialmente dannosa
  • cache: imposta la cache dei token usata da questa istanza authenticationContext. Se questo parametro non è impostato, viene usato un valore predefinito nella cache di memoria

Il nodo MSAL usa invece un oggetto di configurazione di tipo Configuration. Contiene le seguenti proprietà:

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

Come differenza notevole, MSAL non ha un flag per disabilitare la convalida dell'autorità e le autorità vengono sempre convalidate per impostazione predefinita. MSAL confronta l'autorità richiesta con un elenco di autorità note a Microsoft o a un elenco di autorità specificate nella configurazione. Per altre informazioni, vedere Opzioni di configurazione

Passare all'API MSAL

La maggior parte dei metodi pubblici nel nodo ADAL ha equivalenti nel nodo MSAL:

ADAL MSAL Note
acquireToken acquireTokenSilent Rinominato e ora prevede un oggetto account
acquireTokenWithAuthorizationCode acquireTokenByCode
acquireTokenWithClientCredentials acquireTokenByClientCredential
acquireTokenWithRefreshToken acquireTokenByRefreshToken Utile per la migrazione di token di aggiornamento validi
acquireTokenWithDeviceCode acquireTokenByDeviceCode Ora astrae l'acquisizione del codice utente (vedere di seguito)
acquireTokenWithUsernamePassword acquireTokenByUsernamePassword

Tuttavia, alcuni metodi nel nodo ADAL sono deprecati, mentre il nodo MSAL offre nuovi metodi:

ADAL MSAL Note
acquireUserCode N/D Uniti con acquireTokeByDeviceCode (vedere sopra)
N/D acquireTokenOnBehalfOf Nuovo metodo che astrae il flusso OBO
acquireTokenWithClientCertificate N/D Non più necessario perché i certificati vengono assegnati durante l'inizializzazione ora (vedere le opzioni di configurazione)
N/D getAuthCodeUrl Nuovo metodo che astrae la costruzione dell'URL dell'endpoint

Usare ambiti anziché risorse

Una differenza importante tra gli endpoint v1.0 e v2.0 riguarda la modalità di accesso alle risorse. Nel nodo ADAL è 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:

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

Il nodo MSAL supporta solo l'endpoint 2.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:

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

Uno dei vantaggi del modello incentrato sull'ambito è la possibilità di usare ambiti dinamici. Quando si compilano applicazioni con la versione 1.0, è necessario registrare il set completo di autorizzazioni (denominate ambiti statici) richiesti 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

Nel nodo ADAL i callback vengono usati per qualsiasi operazione dopo che l'autenticazione ha esito positivo e viene ottenuta una risposta:

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

Nel nodo MSAL vengono invece usate le promesse:

    const cca = new msal.ConfidentialClientApplication(msalConfig);

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

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

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

Abilitazione della registrazione

Nel nodo ADAL configurare la registrazione separatamente in qualsiasi posizione nel codice:

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

Nel nodo MSAL la registrazione fa parte delle opzioni di configurazione e viene creata con l'inizializzazione dell'istanza del nodo MSAL:

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

Abilitare la memorizzazione nella cache dei token

Nel nodo ADAL era possibile importare una cache dei token in memoria. La cache dei token viene usata come parametro durante l'inizializzazione di un AuthenticationContext oggetto:

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

Per impostazione predefinita, il nodo MSAL usa una cache dei token in memoria. Non è necessario importarlo in modo esplicito; La cache dei token in memoria viene esposta come parte delle ConfidentialClientApplication classi e PublicClientApplication .

const msalTokenCache = publicClientApplication.getTokenCache();

Importante, la cache dei token precedente con il nodo ADAL non sarà trasferiscibile nel nodo MSAL, perché gli schemi della cache non sono compatibili. Tuttavia, puoi usare i token di aggiornamento validi ottenuti in precedenza con il nodo ADAL nel nodo MSAL. Per altre informazioni, vedere la sezione relativa ai token di aggiornamento.

È anche possibile scrivere la cache su disco fornendo il proprio plug-in della cache. Il plug-in cache deve implementare l'interfaccia ICachePlugin. Analogamente alla registrazione, la memorizzazione nella cache fa parte delle opzioni di configurazione e viene creata con l'inizializzazione dell'istanza del nodo MSAL:

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 plug-in cache di esempio può essere implementato come indicato di seguito:

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

Se si sviluppano applicazioni client pubbliche come app desktop, Microsoft Authentication Extensions for Node offre meccanismi sicuri per le applicazioni client per eseguire la serializzazione e la persistenza della cache dei token multipiattaforma. Le piattaforme supportate sono Windows, Mac e Linux.

Nota

Le estensioni di autenticazione Microsoft per Node non sono consigliate per le applicazioni Web, perché possono causare problemi di scalabilità e prestazioni. Le app Web sono invece consigliate per rendere persistente la cache nella sessione.

Rimuovere la logica per i token di aggiornamento

Nel nodo ADAL i token di aggiornamento (RT) sono stati esposti consentendo di sviluppare soluzioni per l'uso di questi token memorizzandoli nella cache e usando il acquireTokenWithRefreshToken metodo . Scenari tipici in cui le RT sono particolarmente rilevanti:

  • Servizi a esecuzione prolungata che eseguono azioni, inclusi l'aggiornamento dei dashboard per conto degli utenti in cui gli utenti non sono più connessi.
  • Scenari WebFarm per consentire al client di portare rt al servizio Web (la memorizzazione nella cache viene eseguita sul lato client, cookie crittografato e non sul lato server).

Il nodo MSAL, insieme ad altri msals, non espone i token di aggiornamento per motivi di sicurezza. MSAL gestisce invece automaticamente i token di aggiornamento. Di conseguenza, non è più necessario compilare la logica per questo. Tuttavia, è possibile usare i token di aggiornamento acquisiti in precedenza (e ancora validi) dalla cache del nodo ADAL per ottenere un nuovo set di token con il nodo MSAL. A tale scopo, il nodo MSAL offre acquireTokenByRefreshToken, che equivale al metodo del acquireTokenWithRefreshToken nodo ADAL:

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

Per altre informazioni, vedere l'esempio di migrazione del nodo ADAL al nodo MSAL.

Nota

È consigliabile eliminare definitivamente la cache dei token del nodo ADAL precedente quando si usano i token di aggiornamento ancora validi per ottenere un nuovo set di token usando il metodo del acquireTokenByRefreshToken nodo MSAL, come illustrato in precedenza.

Gestire errori ed eccezioni

Quando si usa il nodo MSAL, il tipo di errore più comune che si potrebbe riscontrare è l'errore interaction_required . Questo errore viene spesso risolto avviando una richiesta di acquisizione interattiva dei token. Ad esempio, quando si usa acquireTokenSilent, se non sono presenti token di aggiornamento memorizzati nella cache, il nodo MSAL non sarà in grado di acquisire automaticamente un token di accesso. Analogamente, 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 questi casi, la gestione degli interaction_required errori attivando acquireTokenByCode 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 interattiva del token, usando il acquireTokenByCode metodo .

Eseguire l'app

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

npm start

Esempio: Acquisizione di token con nodo ADAL e nodo MSAL

Il frammento di codice seguente illustra un'app Web client riservata nel framework Express.js. Esegue un accesso quando un utente raggiunge la route /authdi autenticazione , acquisisce un token di accesso per Microsoft Graph tramite la /redirect route e quindi visualizza il contenuto del token specificato.

Uso del nodo ADAL Uso del nodo 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!`));

Passaggi successivi