Udostępnij za pośrednictwem


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 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 biblioteki MSAL

W języku ADAL Node inicjalizujesz obiekt AuthenticationContext, który następnie udostępnia metody do wykorzystania w różnych przepływach uwierzytelniania (na przykład acquireTokenWithAuthorizationCode dla aplikacji internetowych). Podczas inicjowania jedynym obowiązkowym parametrem jest URI autorytetu:

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

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

W MSAL Node masz dwie alternatywy: jeśli tworzysz aplikację mobilną lub aplikację na komputer, utworzysz wystąpienie obiektu PublicClientApplication. Konstruktor oczekuje obiektu konfiguracji, który zawiera clientId parametr co najmniej. Biblioteka MSAL ustawia domyślny identyfikator URI dla autorytetu na https://login.microsoftonline.com/common, jeśli nie zostanie on określony.

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

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

Uwaga

Jeśli używasz autorytetu https://login.microsoftonline.com/common w wersji 2.0, umożliwisz użytkownikom logowanie się przy użyciu dowolnej organizacji Microsoft Entra lub osobistego konta Microsoft (MSA). Jeśli w MSAL Node chcesz ograniczyć logowanie do dowolnego konta Microsoft Entra (takie samo zachowanie jak z ADAL Node), użyj https://login.microsoftonline.com/organizations zamiast tego.

Z drugiej strony, jeśli tworzysz aplikację internetową lub aplikację demona, zainicjujesz 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 AuthenticationContext, 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. Więcej informacji można znaleźć w: Inicjalizacja MSAL Node

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ą dla tokenów, która jest używana 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 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);

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 Uwagi
acquireToken acquireTokenSilent Zmieniono nazwę, a teraz oczekuje obiektu konta
acquireTokenWithAuthorizationCode acquireTokenByCode
acquireTokenWithClientCredentials acquireTokenByClientCredential
acquireTokenWithRefreshToken acquireTokenByRefreshToken Pomocne przy migrowaniu 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 Uwagi
acquireUserCode Nie dotyczy Połączono z acquireTokeByDeviceCode (patrz powyżej)
Nie dotyczy acquireTokenOnBehalfOf Nowa metoda, która abstrahuje przepływ 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
    }
);

MSAL Node obsługuje tylko punkt końcowy v2.0. Punkt końcowy w wersji 2.0 wykorzystuje model skupiony 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, gdy ich potrzebujesz (dlatego zakresy dynamiczne nazywamy tak zwanymi). 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 rejestrowanie

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 go jawnie importować; pamięć podręczna tokenów w pamięci jest ujawniana 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 pamięci podręcznej musi zaimplementować 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 desktopowe, rozszerzenia Microsoft Authentication dla Node oferują bezpieczne mechanizmy dla aplikacji klienckich do realizacji międzyplatformowej serializacji i trwałego przechowywania 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ę przechowywanie pamięci podręcznej w sesji.

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

W węźle ADAL tokeny odświeżania (RT) zostały ujawnione, umożliwiając opracowywanie rozwiązań wokół ich użycia, poprzez buforowanie i korzystanie z 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. Jednak można 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 MSAL Node. W tym celu biblioteka MSAL Node oferuje acquireTokenByRefreshToken, który jest odpowiednikiem metody acquireTokenWithRefreshToken biblioteki 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 ADAL Node do MSAL Node.

Uwaga

Zalecamy usunięcie starszej pamięci podręcznej tokenów ADAL Node po wykorzystaniu nadal ważnych tokenów odświeżania, aby uzyskać nowy zestaw tokenów za pomocą metody MSAL Node acquireTokenByRefreshToken, jak pokazano powyżej.

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

Podczas korzystania z MSAL Node najczęstszym typem błędu, z którym możesz się spotkać, 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ć wprowadzone zasady dostępu warunkowego, co wymaga od użytkownika przeprowadzenia uwierzytelniania wieloskładnikowego (MFA). W takich przypadkach obsługa błędu interaction_required poprzez wyzwolenie acquireTokenByCode spowoduje wyświetlenie użytkownikowi komunikatu o konieczności przeprowadzenia uwierzytelniania wieloskładnikowego, co umożliwi mu jego dokończenie.

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ązaniem błędu consent_required jest często inicjowanie interakcyjnego monitu o pozyskiwanie tokenu przy użyciu 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 wejdzie na trasę /auth uwierzytelniania, uzyskuje token dostępu 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