Jak przeprowadzić migrację aplikacji Node.js z biblioteki ADAL do biblioteki MSAL

Biblioteka Microsoft Authentication Library for Node (MSAL Node) jest teraz zalecanym zestawem SDK do włączania uwierzytelniania i autoryzacji dla aplikacji zarejestrowanych w Platforma tożsamości Microsoft. W tym artykule opisano ważne kroki, które należy wykonać, aby przeprowadzić migrację aplikacji z biblioteki uwierzytelniania usługi Active Directory dla węzła (ADAL Node) do węzła MSAL.

Wymagania wstępne

Aktualizowanie ustawień rejestracji aplikacji

Podczas pracy z węzłem biblioteki ADAL prawdopodobnie używano punktu końcowego usługi Azure AD w wersji 1.0. Aplikacje migrujące z biblioteki ADAL do biblioteki MSAL powinny przełączyć się do punktu końcowego usługi Azure AD w wersji 2.0.

Instalowanie i importowanie biblioteki MSAL

  1. zainstaluj pakiet biblioteki MSAL Node za pomocą narzędzia npm:
  npm install @azure/msal-node
  1. Następnie zaimportuj węzeł MSAL w kodzie:
  const msal = require('@azure/msal-node');
  1. Na koniec odinstaluj pakiet ADAL Node i usuń wszystkie odwołania w kodzie:
  npm uninstall adal-node

Inicjowanie biblioteki MSAL

W węźle biblioteki ADAL zainicjujesz AuthenticationContext obiekt, który następnie uwidacznia metody, których można użyć w różnych przepływach uwierzytelniania (na przykład acquireTokenWithAuthorizationCode w przypadku aplikacji internetowych). Podczas inicjowania jedynym obowiązkowym parametrem jest identyfikator URI urzędu:

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

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

W węźle BIBLIOTEKi MSAL istnieją dwie alternatywy: jeśli tworzysz aplikację mobilną lub aplikację klasyczną PublicClientApplication , utworzysz wystąpienie obiektu. Konstruktor oczekuje obiektu konfiguracji, który zawiera clientId parametr co najmniej. Biblioteka MSAL domyślnie określa identyfikator URI https://login.microsoftonline.com/common urzędu, jeśli go nie określisz.

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

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

Uwaga

Jeśli używasz https://login.microsoftonline.com/common urzędu w wersji 2.0, możesz zezwolić użytkownikom na logowanie się przy użyciu dowolnej organizacji firmy Microsoft Entra lub osobistego konta Microsoft (MSA). Jeśli w węźle BIBLIOTEKi MSAL chcesz ograniczyć logowanie do dowolnego konta Microsoft Entra (takie samo zachowanie jak w przypadku węzła biblioteki ADAL), użyj https://login.microsoftonline.com/organizations zamiast tego.

Z drugiej strony, jeśli tworzysz aplikację internetową lub aplikację demona, tworzysz wystąpienie ConfidentialClientApplication obiektu. W przypadku takich aplikacji należy również podać poświadczenia klienta, takie jak klucz tajny klienta lub certyfikat:

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

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

Zarówno PublicClientApplication , jak i ConfidentialClientApplication, w przeciwieństwie do biblioteki AuthenticationContextADAL, jest powiązany z identyfikatorem klienta. Oznacza to, że jeśli masz różne identyfikatory klientów, których chcesz użyć w aplikacji, musisz utworzyć wystąpienie nowego wystąpienia biblioteki MSAL dla każdego z nich. Zobacz, aby uzyskać więcej informacji: Inicjowanie węzła biblioteki MSAL

Konfigurowanie biblioteki MSAL

Podczas kompilowania aplikacji na Platforma tożsamości Microsoft aplikacja będzie zawierać wiele parametrów związanych z uwierzytelnianiem. W węźle AuthenticationContext biblioteki ADAL obiekt ma ograniczoną liczbę parametrów konfiguracji, za pomocą których można utworzyć wystąpienie, podczas gdy pozostałe parametry zawieszają się swobodnie w kodzie (na przykład 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: adres URL identyfikujący urząd tokenu
  • validateAuthority: funkcja, która uniemożliwia kodowi żądanie tokenów od potencjalnie złośliwego urzędu
  • cache: ustawia pamięć podręczną tokenu używaną przez to wystąpienie AuthenticationContext. Jeśli ten parametr nie jest ustawiony, zostanie użyta wartość domyślna w pamięci podręcznej pamięci

Z drugiej strony węzeł MSAL używa obiektu konfiguracji typu Configuration. Zawiera on następujące właściwości:

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

W związku z możliwą różnicą biblioteka MSAL nie ma flagi wyłączania weryfikacji urzędu, a urzędy są zawsze weryfikowane domyślnie. Biblioteka MSAL porównuje żądaną władzę z listą urzędów znanych firmie Microsoft lub listą urzędów określonych w konfiguracji. Zobacz, aby uzyskać więcej informacji: Opcje konfiguracji

Przełączanie do interfejsu API biblioteki MSAL

Większość metod publicznych w węźle biblioteki ADAL ma odpowiedniki w węźle BIBLIOTEKi MSAL:

ADAL BIBLIOTEKA MSAL Uwagi
acquireToken acquireTokenSilent Zmieniono nazwę, a teraz oczekuje obiektu konta
acquireTokenWithAuthorizationCode acquireTokenByCode
acquireTokenWithClientCredentials acquireTokenByClientCredential
acquireTokenWithRefreshToken acquireTokenByRefreshToken Przydatne do migrowania prawidłowych tokenów odświeżania
acquireTokenWithDeviceCode acquireTokenByDeviceCode Teraz abstrakcji pozyskiwania kodu użytkownika (patrz poniżej)
acquireTokenWithUsernamePassword acquireTokenByUsernamePassword

Jednak niektóre metody w węźle biblioteki ADAL są przestarzałe, a biblioteka MSAL Node oferuje nowe metody:

ADAL BIBLIOTEKA MSAL Uwagi
acquireUserCode Nie dotyczy Scalone z acquireTokeByDeviceCode (patrz powyżej)
Nie dotyczy acquireTokenOnBehalfOf Nowa metoda, która abstrakcji przepływu OBO
acquireTokenWithClientCertificate Nie dotyczy Nie są już potrzebne, ponieważ certyfikaty są przypisywane podczas inicjowania (zobacz opcje konfiguracji)
Nie dotyczy getAuthCodeUrl Nowa metoda, która abstrakcyjnie autoryzuje konstrukcję adresu URL punktu końcowego

Używanie zakresów zamiast zasobów

Ważna różnica między punktami końcowymi w wersji 1.0 a 2.0 dotyczy sposobu uzyskiwania dostępu do zasobów. W węźle biblioteki ADAL należy najpierw zarejestrować uprawnienie w portalu rejestracji aplikacji, a następnie zażądać tokenu dostępu dla zasobu (takiego jak Microsoft Graph), jak pokazano poniżej:

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

Węzeł MSAL obsługuje tylko punkt końcowy w wersji 2.0 . Punkt końcowy w wersji 2.0 wykorzystuje model skoncentrowany na zakresie w celu uzyskania dostępu do zasobów. W związku z tym podczas żądania tokenu dostępu dla zasobu należy również określić zakres dla tego zasobu:

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

Jedną z zalet modelu skoncentrowanego na zakresie jest możliwość korzystania z zakresów dynamicznych. Podczas tworzenia aplikacji korzystających z wersji 1.0 konieczne było zarejestrowanie pełnego zestawu uprawnień (nazywanych zakresami statycznymi) wymaganych przez aplikację, aby użytkownik wyraził zgodę w momencie logowania. W wersji 2.0 można użyć parametru zakresu, aby zażądać uprawnień w momencie ich użycia (w związku z tym zakresy dynamiczne). Dzięki temu użytkownik może udzielić przyrostowej zgody na zakresy. Jeśli więc na początku chcesz, aby użytkownik zalogował się do aplikacji i nie potrzebujesz żadnego rodzaju dostępu, możesz to zrobić. Jeśli później potrzebujesz możliwości odczytania kalendarza użytkownika, możesz zażądać zakresu kalendarza w metodach acquireToken i uzyskać zgodę użytkownika. Zobacz, aby uzyskać więcej informacji: Zasoby i zakresy

Używanie obietnic zamiast wywołań zwrotnych

W węźle biblioteki ADAL wywołania zwrotne są używane dla dowolnej operacji po pomyślnym uwierzytelnieniu i uzyskana jest odpowiedź:

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

W węźle BIBLIOTEKi MSAL zamiast tego są używane obietnice:

    const cca = new msal.ConfidentialClientApplication(msalConfig);

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

Można również użyć składni async/await , która jest dostarczana z ES8:

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

Włącz rejestrowanie

W węźle biblioteki ADAL rejestrowanie jest konfigurowane oddzielnie w dowolnym miejscu w kodzie:

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

W węźle biblioteki MSAL rejestrowanie jest częścią opcji konfiguracji i jest tworzone przy użyciu inicjowania wystąpienia biblioteki 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);

Włączanie buforowania tokenów

W węźle biblioteki ADAL była dostępna opcja importowania pamięci podręcznej tokenu w pamięci. Pamięć podręczna tokenu jest używana jako parametr podczas inicjowania AuthenticationContext obiektu:

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

Węzeł MSAL domyślnie używa pamięci podręcznej tokenu w pamięci. Nie trzeba jawnie go importować; Pamięć podręczna tokenu ConfidentialClientApplication w pamięci jest uwidaczniona w ramach klas i PublicClientApplication .

const msalTokenCache = publicClientApplication.getTokenCache();

Co ważne, poprzednia pamięć podręczna tokenów z węzłem biblioteki ADAL nie będzie można przenieść do węzła MSAL, ponieważ schematy pamięci podręcznej są niezgodne. Można jednak użyć prawidłowych tokenów odświeżania uzyskanych wcześniej z węzła biblioteki ADAL w środowisku MSAL Node. Aby uzyskać więcej informacji, zobacz sekcję dotyczącą tokenów odświeżania .

Możesz również zapisać pamięć podręczną na dysku, podając własną wtyczkę pamięci podręcznej. Wtyczka pamięci podręcznej musi zaimplementować interfejs ICachePlugin. Podobnie jak rejestrowanie, buforowanie jest częścią opcji konfiguracji i jest tworzone przy użyciu inicjowania wystąpienia biblioteki 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);

Przykładową wtyczkę pamięci podręcznej można zaimplementować w następujący sposób:

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

Jeśli tworzysz publiczne aplikacje klienckie, takie jak aplikacje klasyczne, rozszerzenia uwierzytelniania firmy Microsoft dla środowiska Node oferują bezpieczne mechanizmy dla aplikacji klienckich do wykonywania serializacji i trwałości międzyplatformowej pamięci podręcznej tokenów. Obsługiwane platformy to Systemy Windows, Mac i Linux.

Uwaga

Rozszerzenia uwierzytelniania firmy Microsoft dla środowiska Node niezalecane w przypadku aplikacji internetowych, ponieważ mogą prowadzić do problemów ze skalowaniem i wydajnością. Zamiast tego zaleca się utrwalanie pamięci podręcznej w sesji.

Usuwanie logiki wokół tokenów odświeżania

W węźle biblioteki ADAL tokeny odświeżania (RT) zostały ujawnione, umożliwiając opracowywanie rozwiązań dotyczących używania tych tokenów przez buforowanie ich i używanie acquireTokenWithRefreshToken metody . Typowe scenariusze, w których RTs są szczególnie istotne:

  • Długotrwałe usługi, które wykonują akcje, w tym odświeżanie pulpitów nawigacyjnych w imieniu użytkowników, którzy nie są już połączeni.
  • Scenariusze WebFarm umożliwiające klientowi przełączenie rt do usługi internetowej (buforowanie odbywa się po stronie klienta, zaszyfrowany plik cookie, a nie po stronie serwera).

Węzeł MSAL wraz z innymi listami MSALS nie uwidacznia tokenów odświeżania ze względów bezpieczeństwa. Zamiast tego biblioteka MSAL obsługuje odświeżanie tokenów. W związku z tym nie trzeba już tworzyć logiki. Można jednak użyć wcześniej uzyskanych (i nadal prawidłowych) tokenów odświeżania z pamięci podręcznej węzła biblioteki ADAL, aby uzyskać nowy zestaw tokenów z węzłem MSAL. W tym celu węzeł MSAL oferuje element acquireTokenByRefreshToken, który jest odpowiednikiem metody biblioteki acquireTokenWithRefreshToken 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);
});

Aby uzyskać więcej informacji, zapoznaj się z przykładem migracji węzła biblioteki ADAL do biblioteki MSAL Node.

Uwaga

Zalecamy zniszczenie starszej pamięci podręcznej tokenów biblioteki ADAL Node po wykorzystaniu nadal prawidłowych tokenów odświeżania w celu uzyskania nowego zestawu tokenów przy użyciu metody biblioteki MSAL Node acquireTokenByRefreshToken , jak pokazano powyżej.

Obsługa błędów i wyjątków

W przypadku korzystania z węzła MSAL najczęstszym typem błędu może być interaction_required błąd. Ten błąd jest często rozwiązywany przez zainicjowanie interakcyjnego monitu o uzyskanie tokenu. Na przykład w przypadku używania metody acquireTokenSilent, jeśli nie ma buforowanych tokenów odświeżania, węzeł MSAL nie będzie mógł uzyskać tokenu dostępu w trybie dyskretnym. Podobnie internetowy interfejs API, do którego próbujesz uzyskać dostęp, może mieć zasady dostępu warunkowego, co wymaga od użytkownika przeprowadzenia uwierzytelniania wieloskładnikowego (MFA). W takich przypadkach obsługa interaction_required błędu przez wyzwolenie spowoduje wyświetlenie monitu użytkownika o uwierzytelnianie wieloskładnikowe acquireTokenByCode , co umożliwi mu pełne filtrowanie.

Kolejnym typowym błędem, który może wystąpić, jest consent_required, który występuje, gdy uprawnienia wymagane do uzyskania tokenu dostępu dla chronionego zasobu nie są wyrażane przez użytkownika. Podobnie jak w interaction_requiredsystemie rozwiązanie błędu consent_required często inicjuje interakcyjny monit o pozyskiwanie tokenu acquireTokenByCode przy użyciu metody .

Uruchom aplikację

Po zakończeniu zmian uruchom aplikację i przetestuj scenariusz uwierzytelniania:

npm start

Przykład: uzyskiwanie tokenów za pomocą węzła biblioteki ADAL a węzła BIBLIOTEKi MSAL

Poniższy fragment kodu przedstawia poufną aplikację internetową klienta w strukturze Express.js. Wykonuje logowanie, gdy użytkownik osiągnie trasę /authuwierzytelniania , uzyskuje token dostępu dla programu Microsoft Graph za pośrednictwem /redirect trasy, a następnie wyświetla zawartość wspomnianego tokenu.

Korzystanie z węzła biblioteki ADAL Korzystanie z biblioteki MSAL Node
// 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!`));

Następne kroki