Migrowanie aplikacji Node.js z biblioteki ADAL do biblioteki MSAL

Microsoft Authentication Library for Node (MSAL Node) to zalecany zestaw SDK umożliwiający włączenie 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 Active Directory Authentication Library for Node (ADAL Node) do węzła MSAL.

Wymagania wstępne

Migracja aplikacji z ADAL Node do MSAL Node

Ogólne uwagi dotyczące różnic między biblioteką ADAL a biblioteką MSAL, a także ważnymi datami i często zadawanymi pytaniami, zobacz: Migrowanie aplikacji do biblioteki Microsoft Authentication Library (MSAL)

Aktualizowanie ustawień rejestracji aplikacji

Podczas pracy z węzłem Node ADAL prawdopodobnie używano punktu końcowego Azure AD w wersji 1.0. Przeprowadź migrację aplikacji z biblioteki ADAL do biblioteki MSAL, aby użyć punktu końcowego platformy tożsamości firmy Microsoft.

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 MSAL Node do swojego kodu.
  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 MSAL

W ADAL Node zainicjujesz AuthenticationContext obiekt, który następnie udostępnia metody, których można użyć w różnych przepływach uwierzytelniania (na przykład acquireTokenWithAuthorizationCode dla aplikacji internetowych). Podczas inicjowania jedynym obowiązkowym parametrem jest adres URI autorytetu:

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

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

W MSAL dla Node.js masz dwie możliwości: jeśli tworzysz aplikację mobilną lub aplikację na komputer PublicClientApplication, utworzysz wystąpienie obiektu. Konstruktor oczekuje obiektu konfiguracji , który zawiera clientId parametr co najmniej. Biblioteka MSAL domyślnie ustawia identyfikator URI autorytetu na https://login.microsoftonline.com/common, jeśli go nie określisz.

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

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

Note

Jeśli używasz autorytetu https://login.microsoftonline.com/common w wersji v2.0, możesz zezwolić użytkownikom na logowanie się za pomocą dowolnej organizacji Microsoft Entra lub osobistego konta Microsoft (MSA). W węźle biblioteki MSAL, jeśli chcesz ograniczyć logowanie do dowolnego konta Microsoft Entra (takie samo zachowanie jak w przypadku węzła biblioteki ADAL), zamiast tego użyj polecenia https://login.microsoftonline.com/organizations.

Z drugiej strony, jeśli tworzysz aplikację internetową lub aplikację demona, inicjujesz obiekt ConfidentialClientApplication. 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, są powiązane z identyfikatorem klienta. Oznacza to, że jeśli masz różne identyfikatory klientów, których chcesz użyć w aplikacji, musisz utworzyć nowe wystąpienie biblioteki MSAL dla każdego z nich. Zobacz więcej: Inicjalizacja MSAL Node

Konfiguracja MSAL

Podczas kompilowania aplikacji na Platforma tożsamości Microsoft aplikacja będzie zawierać wiele parametrów związanych z uwierzytelnianiem. W ADAL Node obiekt ma ograniczoną liczbę parametrów konfiguracji, za pomocą których można go zainicjalizować, podczas gdy pozostałe parametry pozostają swobodnie w kodzie (na przykład AuthenticationContext):

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 wykorzystana domyślna pamięć podręczna w pamięci.

Z drugiej strony węzeł MSAL używa obiektu konfiguracji typu Configuration. Zawiera 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);

Jako znacząca różnica, biblioteka MSAL nie ma flagi do wyłączania weryfikacji autorytetu, który jest zawsze weryfikowany domyślnie. Biblioteka MSAL porównuje żądany przez Ciebie autorytet z listą autorytetów znanych Microsoftowi lub listą autorytetów określonych w Twojej konfiguracji. Zobacz, aby uzyskać więcej informacji: Opcje konfiguracji

Przełącz na interfejs API MSAL

Większość metod publicznych w ADAL Node ma odpowiedniki w MSAL Node.

ADAL MSAL Notatki
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 ADAL Node są przestarzałe, podczas gdy MSAL Node oferuje nowe metody.

ADAL MSAL Notatki
acquireUserCode N/A Połączone z acquireTokeByDeviceCode (patrz powyżej)
N/A acquireTokenOnBehalfOf Nowa metoda, która dokonuje abstrakcji przepływu OBO
acquireTokenWithClientCertificate N/A Nie są już potrzebne, ponieważ certyfikaty są przypisywane podczas inicjowania (zobacz opcje konfiguracji)
N/A 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 v2.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żywaj 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 MSAL Node używane są 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 logowanie

W Node.js ADAL konfiguracja logowania jest wykonywana 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 MSAL Node, rejestrowanie jest częścią opcji konfiguracji i jest tworzone podczas inicjalizacji wystąpienia 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 ADAL Node była możliwość importowania pamięci podręcznej tokenu przechowywanej w pamięci operacyjnej. 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 tokenów w pamięci jest udostępniana jako część klas ConfidentialClientApplication i PublicClientApplication.

const msalTokenCache = publicClientApplication.getTokenCache();

Co ważne, poprzednia pamięć podręczna tokenów z biblioteki ADAL nie będzie można przenieść do biblioteki MSAL, ponieważ schematy pamięci podręcznej są niezgodne. Można jednak użyć prawidłowych tokenów odświeżania uzyskanych wcześniej przy użyciu ADAL Node w 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 bufora musi implementować interfejs ICachePlugin. Podobnie jak logowanie, buforowanie jest częścią opcji konfiguracji i jest tworzone podczas inicjalizacji 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 Windows, Mac i Linux.

Note

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

Usuwanie logiki związanej z tokenami odświeżania

W ADAL Node, tokeny odświeżania (RT) zostały udostępnione, umożliwiając opracowywanie rozwiązań dotyczących używania tych tokenów przez ich buforowanie i użycie metody acquireTokenWithRefreshToken. 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 przekazanie RT do usługi internetowej (buforowanie odbywa się po stronie klienta, używany jest zaszyfrowany plik cookie, a nie po stronie serwera).

MSAL Node, podobnie jak inne MSAL, nie ujawnia tokenów odświeżania z powodów bezpieczeństwa. Zamiast tego, to MSAL zajmuje się procesem odświeżania tokenów dla Ciebie. 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 ADAL Node, aby uzyskać nowy zestaw tokenów z biblioteki MSAL Node. Aby to zrobić, MSAL Node oferuje acquireTokenByRefreshToken, co jest odpowiednikiem metody acquireTokenWithRefreshToken z 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 z biblioteki ADAL Node do biblioteki MSAL Node.

Note

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

Podczas korzystania z MSAL Node, najczęstszym rodzajem błędu, który możesz napotkać, jest błąd interaction_required. 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 acquireTokenByCode spowoduje wyświetlenie użytkownikowi prośby o uwierzytelnianie wieloskładnikowe, co umożliwi mu jego przeprowadzenie.

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_required, rozwiązanie błędu consent_required często polega na zainicjowaniu interakcyjnego monitu o pozyskiwanie tokenu za pomocą metody acquireTokenByCode.

Uruchom aplikację

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

npm start

Przykład: uzyskiwanie tokenów za pomocą ADAL Node vs. MSAL Node

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

Korzystanie z ADAL Node 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