Partilhar via


Como migrar um aplicativo Node.js da ADAL para a MSAL

A Biblioteca de Autenticação da Microsoft para Nó (Nó MSAL) agora é o SDK recomendado para habilitar a autenticação e a autorização para seus aplicativos registrados na plataforma de identidade da Microsoft. Este artigo aborda as etapas importantes que você precisa percorrer para migrar seus aplicativos da Biblioteca de Autenticação do Ative Directory para Nó (Nó ADAL) para o Nó MSAL.

Pré-requisitos

  • Nó versão 10, 12, 14, 16 ou 18. Veja a nota sobre o suporte de versão

Atualizar as configurações de registro do aplicativo

Ao trabalhar com o Nó ADAL, você provavelmente estava usando o ponto de extremidade do Azure AD v1.0. Os aplicativos que migram da ADAL para a MSAL devem alternar para o ponto de extremidade do Azure AD v2.0.

Instalar e importar MSAL

  1. instale o pacote MSAL Node via npm:
  npm install @azure/msal-node
  1. Depois disso, importe o nó MSAL em seu código:
  const msal = require('@azure/msal-node');
  1. Finalmente, desinstale o pacote ADAL Node e remova todas as referências no seu código:
  npm uninstall adal-node

Inicializar MSAL

No nó ADAL, você inicializa um AuthenticationContext objeto, que expõe os métodos que você pode usar em diferentes fluxos de autenticação (por exemplo, acquireTokenWithAuthorizationCode para aplicativos Web). Ao inicializar, o único parâmetro obrigatório é o URI de autoridade:

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

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

No MSAL Node, você tem duas alternativas: se estiver criando um aplicativo móvel ou um aplicativo de desktop, você instancia um PublicClientApplication objeto. O construtor espera um objeto de configuração que contém o clientId parâmetro no mínimo. O MSAL padroniza o URI da autoridade se https://login.microsoftonline.com/common você não o especificar.

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

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

Nota

Se você usar a https://login.microsoftonline.com/common autoridade na v2.0, permitirá que os usuários entrem com qualquer organização do Microsoft Entra ou uma conta pessoal da Microsoft (MSA). No Nó MSAL, se você quiser restringir o login a qualquer conta do Microsoft Entra (mesmo comportamento do Nó ADAL), use https://login.microsoftonline.com/organizations em vez disso.

Por outro lado, se você estiver criando um aplicativo Web ou um aplicativo daemon, você instanciará um ConfidentialClientApplication objeto. Com esses aplicativos, você também precisa fornecer uma credencial de cliente, como um segredo de cliente ou um certificado:

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

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

Ambos e PublicClientApplicationConfidentialClientApplication, ao contrário da AuthenticationContextADAL, estão vinculados a um ID de cliente. Isso significa que, se você tiver IDs de cliente diferentes que você gosta de usar em seu aplicativo, você precisa instanciar uma nova instância MSAL para cada um. Consulte para mais: Inicialização do nó MSAL

Configurar o MSAL

Ao criar aplicativos na plataforma de identidade da Microsoft, seu aplicativo conterá muitos parâmetros relacionados à autenticação. No ADAL Node, o AuthenticationContext objeto tem um número limitado de parâmetros de configuração com os quais você pode instanciá-lo, enquanto os parâmetros restantes travam livremente em seu código (por exemplo, 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 que identifica uma autoridade de token
  • validateAuthority: um recurso que impede que seu código solicite tokens de uma autoridade potencialmente mal-intencionada
  • cache: define o cache de token usado por esta instância AuthenticationContext. Se esse parâmetro não estiver definido, será usado um padrão no cache de memória

MSAL Node, por outro lado, usa um objeto de configuração do tipo Configuration. Contém as seguintes propriedades:

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

Como uma diferença notável, o MSAL não tem um sinalizador para desativar a validação de autoridade e as autoridades são sempre validadas por padrão. A MSAL compara a autoridade solicitada com uma lista de autoridades conhecidas pela Microsoft ou com uma lista de autoridades especificadas na sua configuração. Consulte para mais: Opções de configuração

Mudar para a API MSAL

A maioria dos métodos públicos no Nó ADAL tem equivalentes no Nó MSAL:

ADAL MSAL Notas
acquireToken acquireTokenSilent Renomeado e agora espera um objeto de conta
acquireTokenWithAuthorizationCode acquireTokenByCode
acquireTokenWithClientCredentials acquireTokenByClientCredential
acquireTokenWithRefreshToken acquireTokenByRefreshToken Útil para migrar tokens de atualização válidos
acquireTokenWithDeviceCode acquireTokenByDeviceCode Agora abstrai a aquisição de código de usuário (veja abaixo)
acquireTokenWithUsernamePassword acquireTokenByUsernamePassword

No entanto, alguns métodos no Nó ADAL são preteridos, enquanto o Nó MSAL oferece novos métodos:

ADAL MSAL Notas
acquireUserCode N/A Mesclado com acquireTokeByDeviceCode (ver acima)
N/A acquireTokenOnBehalfOf Um novo método que abstrai o fluxo OBO
acquireTokenWithClientCertificate N/A Não é mais necessário, pois os certificados são atribuídos durante a inicialização agora (consulte as opções de configuração)
N/A getAuthCodeUrl Um novo método que abstrai autorizar a construção de URL de ponto de extremidade

Usar escopos em vez de recursos

Uma diferença importante entre os pontos de extremidade v1.0 vs. v2.0 é sobre como os recursos são acessados. No Nó ADAL, você primeiro registraria uma permissão no portal de registro de aplicativo e, em seguida, solicitaria um token de acesso para um recurso (como o Microsoft Graph), conforme mostrado abaixo:

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

O nó MSAL suporta apenas o ponto de extremidade v2.0 . O ponto de extremidade v2.0 emprega um modelo centrado no escopo para acessar recursos. Assim, quando você solicita um token de acesso para um recurso, você também precisa especificar o escopo para esse recurso:

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

Uma vantagem do modelo centrado no escopo é a capacidade de usar escopos dinâmicos. Ao criar aplicativos usando a v1.0, você precisava registrar o conjunto completo de permissões (chamadas de escopos estáticos) exigidas pelo aplicativo para o usuário consentir no momento do login. Na v2.0, você pode usar o parâmetro scope para solicitar as permissões no momento desejado (portanto, escopos dinâmicos). Isso permite que o usuário forneça consentimento incremental para escopos. Então, se no início você quiser apenas que o usuário entre no seu aplicativo e você não precisa de nenhum tipo de acesso, você pode fazê-lo. Se mais tarde você precisar da capacidade de ler o calendário do usuário, você pode solicitar o escopo do calendário nos métodos acquireToken e obter o consentimento do usuário. Veja mais: Recursos e escopos

Use promessas em vez de retornos de chamada

No nó ADAL, os retornos de chamada são usados para qualquer operação depois que a autenticação é bem-sucedida e uma resposta é obtida:

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

No MSAL Node, as promessas são usadas em vez disso:

    const cca = new msal.ConfidentialClientApplication(msalConfig);

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

Você também pode usar a sintaxe async/await que vem com o ES8:

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

Ativar registo

No nó ADAL, você configura o registro separadamente em qualquer lugar do código:

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

No nó MSAL, o log faz parte das opções de configuração e é criado com a inicialização da instância do nó 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);

Habilitar cache de token

No ADAL Node, você tinha a opção de importar um cache de token na memória. O cache de token é usado como um parâmetro ao inicializar um AuthenticationContext objeto:

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

O nó MSAL usa um cache de token na memória por padrão. Você não precisa importá-lo explicitamente; O cache de token na memória é exposto como parte das ConfidentialClientApplication classes e PublicClientApplication .

const msalTokenCache = publicClientApplication.getTokenCache();

É importante ressaltar que seu cache de token anterior com o Nó ADAL não será transferível para o Nó MSAL, pois os esquemas de cache são incompatíveis. No entanto, você pode usar os tokens de atualização válidos que seu aplicativo obteve anteriormente com o Nó ADAL no Nó MSAL. Consulte a seção sobre tokens de atualização para saber mais.

Você também pode gravar seu cache no disco fornecendo seu próprio plug-in de cache. O plug-in de cache deve implementar a interface ICachePlugin. Assim como o registro em log, o cache faz parte das opções de configuração e é criado com a inicialização da instância do nó 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);

Um exemplo de plug-in de cache pode ser implementado como abaixo:

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 você estiver desenvolvendo aplicativos cliente públicos, como aplicativos de área de trabalho, as Extensões de Autenticação da Microsoft para Nó oferecem mecanismos seguros para que aplicativos cliente executem serialização e persistência de cache de token entre plataformas. As plataformas suportadas são Windows, Mac e Linux.

Nota

As Extensões de Autenticação da Microsoft para Nó não são recomendadas para aplicativos Web, pois podem levar a problemas de escala e desempenho. Em vez disso, recomenda-se que os aplicativos Web persistam o cache na sessão.

Remova a lógica em torno de tokens de atualização

No ADAL Node, os tokens de atualização (RT) foram expostos, permitindo que você desenvolva soluções em torno do uso desses tokens, armazenando-os em cache e usando o acquireTokenWithRefreshToken método. Cenários típicos em que as IR são especialmente relevantes:

  • Serviços de longa execução que executam ações, incluindo a atualização de painéis em nome dos usuários onde os usuários não estão mais conectados.
  • Cenários de WebFarm para permitir que o cliente traga o RT para o serviço Web (o cache é feito do lado do cliente, do cookie criptografado e não do lado do servidor).

O MSAL Node, juntamente com outros MSALs, não expõe tokens de atualização por motivos de segurança. Em vez disso, a MSAL lida com tokens de atualização para você. Como tal, você não precisa mais construir lógica para isso. No entanto, você pode usar seus tokens de atualização adquiridos anteriormente (e ainda válidos) do cache do Nó ADAL para obter um novo conjunto de tokens com o Nó MSAL. Para fazer isso, o MSAL Node oferece acquireTokenByRefreshToken, que é equivalente ao método do 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);
});

Para obter mais informações, consulte o Exemplo de migração do Nó ADAL para o Nó MSAL.

Nota

Recomendamos que você destrua o cache de token do Nó ADAL mais antigo depois de utilizar os tokens de atualização ainda válidos acquireTokenByRefreshToken para obter um novo conjunto de tokens usando o método do Nó MSAL, conforme mostrado acima.

Lidar com erros e exceções

Ao usar o nó MSAL, o tipo mais comum de erro que você pode enfrentar é o interaction_required erro. Esse erro geralmente é resolvido iniciando um prompt de aquisição de token interativo. Por exemplo, ao usar acquireTokenSilento , se não houver tokens de atualização em cache, o Nó MSAL não poderá adquirir um token de acesso silenciosamente. Da mesma forma, a API da Web que você está tentando acessar pode ter uma política de Acesso Condicional em vigor, exigindo que o usuário execute a autenticação multifator (MFA). Nesses casos, o tratamento do interaction_required erro por acionamento acquireTokenByCode solicitará ao usuário MFA, permitindo que ele o complete.

Outro erro comum que você pode enfrentar é consent_requiredo , que ocorre quando as permissões necessárias para obter um token de acesso para um recurso protegido não são consentidas pelo usuário. Como no interaction_required, a solução para consent_required o erro geralmente é iniciar um prompt de aquisição de token interativo, usando o acquireTokenByCode método.

Executar a aplicação

Quando as alterações estiverem concluídas, execute o aplicativo e teste seu cenário de autenticação:

npm start

Exemplo: Adquirindo tokens com o Nó ADAL vs. Nó MSAL

O trecho abaixo demonstra um aplicativo Web cliente confidencial na estrutura Express.js. Ele executa uma entrada quando um usuário atinge a rota /authde autenticação, adquire um token de acesso para o Microsoft Graph por meio da /redirect rota e, em seguida, exibe o conteúdo do referido token.

Usando o nó ADAL Usando o nó 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!`));

Próximos passos