Tutorial: Chamar uma API Web do aplicativo daemon .Node.js

Este tutorial é a parte final de uma série que demonstra como preparar seu aplicativo cliente daemon Node.js usando o fluxo de concessão de credenciais do cliente OAuth (Open Authorization) 2.0 e configurá-lo para adquirir um token de acesso para chamar uma API Web. Na parte 1 desta série, você registrou uma API Web e um aplicativo daemon no centro de administração do Microsoft Entra e concedeu permissões. Esta etapa final demonstra como compilar um aplicativo Node.js usando a MSAL (biblioteca de autenticação da Microsoft) para Node para simplificar a adição de autorização ao seu aplicativo.

Neste tutorial;

  • Crie um aplicativo Node.js no Visual Studio Code e instale dependências.
  • Habilite o aplicativo Node.js para adquirir um token de acesso para chamar uma API Web.

Pré-requisitos

Criar o projeto daemon do Node.js

Crie uma pasta para hospedar seu aplicativo daemon do Node.js, como ciam-call-api-node-daemon:

  1. No seu terminal, altere o diretório para a pasta do aplicativo daemon do Node, como cd ciam-call-api-node-daemon, e execute npm init -y. Esse comando cria um arquivo package.json padrão para seu projeto Node.js. Esse comando cria um arquivo package.json padrão para seu projeto Node.js.

  2. Crie pastas e arquivos adicionais para obter a seguinte estrutura de projeto:

        ciam-call-api-node-daemon/
        ├── auth.js
        └── authConfig.js
        └── fetch.js
        └── index.js 
        └── package.json
    

Instalar dependências do aplicativo

No terminal, instale os pacotes axios, yargs e @azure/msal-node com o seguinte comando:

npm install axios yargs @azure/msal-node   

Criar objeto de configuração da MSAL

No editor de código, abra o arquivo authConfig.js e adicione o seguinte código:

require('dotenv').config();

/**
 * Configuration object to be passed to MSAL instance on creation.
 * For a full list of MSAL Node configuration parameters, visit:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/configuration.md
 */    
const msalConfig = {
    auth: {
        clientId: process.env.CLIENT_ID || 'Enter_the_Application_Id_Here', // 'Application (client) ID' of app registration in Azure portal - this value is a GUID
        authority: process.env.AUTHORITY || 'https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/', // Replace "Enter_the_Tenant_Subdomain_Here" with your tenant subdomain
        clientSecret: process.env.CLIENT_SECRET || 'Enter_the_Client_Secret_Here', // Client secret generated from the app 
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: 'Info',
        },
    },
};    
const protectedResources = {
    apiToDoList: {
        endpoint: process.env.API_ENDPOINT || 'https://localhost:44351/api/todolist',
        scopes: [process.env.SCOPES || 'api://Enter_the_Web_Api_Application_Id_Here'],
    },
};

module.exports = {
    msalConfig,
    protectedResources,
};

O objeto msalConfig contém um conjunto de opções de configuração que você usará para personalizar o comportamento do fluxo de autorização.

No arquivo authConfig.js, substitua:

  • Enter_the_Application_Id_Here pela ID do aplicativo (cliente) do aplicativo daemon cliente que você registrou anteriormente.

  • Enter_the_Tenant_Subdomain_Here e substitua-o pelo subdomínio diretório (locatário). Por exemplo, se o domínio primário do locatário for contoso.onmicrosoft.com, use contoso. Se você não tiver o nome do locatário, saiba como ler os detalhes do locatário.

  • Enter_the_Client_Secret_Here pelo valor do segredo do aplicativo daemon do cliente que você copiou anteriormente.

  • Enter_the_Web_Api_Application_Id_Here pela ID do aplicativo (cliente) do aplicativo de API Web que você copiou anteriormente.

Observe que a propriedade scopes na variável protectedResources é o identificador de recurso (URI da ID do aplicativo) da API Web que você registrou anteriormente. O URI de escopo completo é semelhante a api://Enter_the_Web_Api_Application_Id_Here/.default.

Adquirir um token de acesso

No editor de código, abra o arquivo auth.js e adicione o seguinte código:

const msal = require('@azure/msal-node');
const { msalConfig, protectedResources } = require('./authConfig');
/**
 * With client credentials flows permissions need to be granted in the portal by a tenant administrator.
 * The scope is always in the format '<resource-appId-uri>/.default'. For more, visit:
 * https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
 */
const tokenRequest = {
    scopes: [`${protectedResources.apiToDoList.scopes}/.default`],
};

const apiConfig = {
    uri: protectedResources.apiToDoList.endpoint,
};

/**
 * Initialize a confidential client application. For more info, visit:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/initialize-confidential-client-application.md
 */
const cca = new msal.ConfidentialClientApplication(msalConfig);
/**
 * Acquires token with client credentials.
 * @param {object} tokenRequest
 */
async function getToken(tokenRequest) {
    return await cca.acquireTokenByClientCredential(tokenRequest);
}

module.exports = {
    apiConfig: apiConfig,
    tokenRequest: tokenRequest,
    getToken: getToken,
};

No código:

  • Prepare os objetos tokenRequest e apiConfig. O tokenRequest contém o escopo para o qual você solicitará um token de acesso. O escopo se parece com api://Enter_the_Web_Api_Application_Id_Here/.default. O objeto apiConfig contém o ponto de extremidade para sua API Web. Saiba mais sobre o fluxo de credenciais do cliente OAuth 2.0.

  • Você criará uma instância de cliente confidencial passando o objeto msalConfig para o construtor da classe ConfidentialClientApplication.

    const cca = new msal.ConfidentialClientApplication(msalConfig);
    
  • Em seguida, usará a função acquireTokenByClientCredential para adquirir um token de acesso. Essa lógica será implementada na função getToken:

    cca.acquireTokenByClientCredential(tokenRequest);
    

Depois de adquirir um token de acesso, você pode prosseguir chamando uma API.

Chamar uma API

No editor de código, abra o arquivo fetch.js e adicione o seguinte código:

const axios = require('axios');

/**
 * Calls the endpoint with authorization bearer token.
 * @param {string} endpoint
 * @param {string} accessToken 
 */
async function callApi(endpoint, accessToken) {

    const options = {
        headers: {
            Authorization: `Bearer ${accessToken}`
        }
    };

    console.log('request made to web API at: ' + new Date().toString());

    try {
        const response = await axios.get(endpoint, options);
        return response.data;
    } catch (error) {
        console.log(error)
        return error;
    }
};

module.exports = {
    callApi: callApi
};

Nesse código, você faz uma chamada para a API Web, passando o token de acesso como um token de portador no cabeçalho Authorization da solicitação:

 Authorization: `Bearer ${accessToken}`

Você usará o token de acesso adquirido anteriormente em Adquirir um token de acesso.

Depois que a API Web receber a solicitação, ela a avaliará e irá determinar que é uma solicitação de aplicativo. Se o token de acesso for válido, a API Web retornará os dados solicitados. Caso contrário, a API retornará um erro HTTP 401 Unauthorized.

Finalizar seu aplicativo daemon

No editor de código, abra o arquivo index.js e adicione o seguinte código:

#!/usr/bin/env node

// read in env settings

require('dotenv').config();

const yargs = require('yargs');
const fetch = require('./fetch');
const auth = require('./auth');

const options = yargs
    .usage('Usage: --op <operation_name>')
    .option('op', { alias: 'operation', describe: 'operation name', type: 'string', demandOption: true })
    .argv;

async function main() {
    console.log(`You have selected: ${options.op}`);

    switch (yargs.argv['op']) {
        case 'getToDos':
            try {
                const authResponse = await auth.getToken(auth.tokenRequest);
                const todos = await fetch.callApi(auth.apiConfig.uri, authResponse.accessToken);                
            } catch (error) {
                console.log(error);
            }

            break;
        default:
            console.log('Select an operation first');
            break;
    }
};

main();

Esse código é o ponto de entrada para seu aplicativo. Você utilizará a biblioteca de análise de argumentos de linha de comando yargs JavaScript para aplicativos Node.js, a fim de obter interativamente um token de acesso e, em seguida, chamar uma API. Você usará as funções getToken e callApi que definiu anteriormente:

const authResponse = await auth.getToken(auth.tokenRequest);
const todos = await fetch.callApi(auth.apiConfig.uri, authResponse.accessToken);                

Executar e testar o aplicativo daemon e a API

Neste ponto, você está pronto para testar seu aplicativo daemon cliente e a API Web:

  1. Use as etapas aprendidas no tutorial Proteger uma ASP.NET Web API para iniciar sua API Web. Sua API Web agora está pronta para atender às solicitações do cliente. Se você não executar sua API Web na porta 44351 conforme especificado no arquivo authConfig.js, atualize o arquivo authConfig.js para usar o número correto da porta da API Web.

  2. Em seu terminal, verifique se você está na pasta do projeto que contém o aplicativo daemon Node.js, como ciam-call-api-node-daemon, e execute o seguinte comando:

    node . --op getToDos
    

Se o aplicativo daemon e a API Web forem executados com sucesso, você deverá encontrar os dados retornados pela variável todos do ponto de extremidade da API Web, semelhantes à matriz JSON a seguir, na janela do seu console:

{
    id: 1,
    owner: '3e8....-db63-43a2-a767-5d7db...',
    description: 'Pick up grocery'
},
{
    id: 2,
    owner: 'c3cc....-c4ec-4531-a197-cb919ed.....',
    description: 'Finish invoice report'
},
{
    id: 3,
    owner: 'a35e....-3b8a-4632-8c4f-ffb840d.....',
    description: 'Water plants'
}

Próxima etapa