Condividi tramite


Esercitazione: Chiamare un'API Web dall'applicazione daemon Node.js

Questa esercitazione è la parte finale di una serie che mostra come preparare l'app daemon client Node.js tramite il flusso di concessione delle credenziali client OAuth (Open Authorization) 2.0 e quindi configurarla per acquisire un token di accesso per chiamare un'API Web. Nella prima parte di questa serie sono state registrate un'API Web e un'app daemon nell'Interfaccia di amministrazione di Microsoft Entra e sono state concesse le autorizzazioni. Questo passaggio finale mostra come compilare un'applicazione Node.js usando Microsoft Authentication Library (MSAL) for Node per semplificare l'aggiunta dell'autorizzazione all'app.

Contenuto dell'esercitazione:

  • Creare un'app Node.js in Visual Studio Code e quindi installare le dipendenze.
  • Abilitare l'app Node.js per acquisire un token di accesso per chiamare un'API Web.

Prerequisiti

Creare il progetto daemon Node.js

Creare una cartella per ospitare l'applicazione daemon Node.js, ad esempio ciam-call-api-node-daemon:

  1. Nel terminale modificare la directory nella cartella dell'app daemon del nodo, ad esempio cd ciam-call-api-node-daemon, quindi eseguire npm init -y. Questo comando crea un file package.json predefinito per il progetto Node.js. Questo comando crea un file package.json predefinito per il progetto Node.js.

  2. Creare ulteriori cartelle e file per ottenere la struttura di progetto seguente:

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

Installare le dipendenze dell'app

Nel terminale installare i pacchetti axios, yargs e @azure/msal-node eseguendo il comando seguente:

npm install axios yargs @azure/msal-node   

Creare un oggetto di configurazione MSAL

Nell'editor di codice, aprire il file authConfig.js quindi aggiungere il codice seguente:

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

L'oggetto msalConfig contiene un set di opzioni di configurazione usate per personalizzare il comportamento del flusso di autenticazione.

Nel file authConfig.js, sostituire:

  • Enter_the_Application_Id_Here con l’ID applicazione (client) dell'app daemon client registrata in precedenza.

  • Enter_the_Tenant_Subdomain_Here e sostituirlo con il sottodominio della directory (tenant). Ad esempio, se il dominio primario del tenant è contoso.onmicrosoft.com, usare contoso. Se non si dispone del nome del tenant, scoprire come leggere i dettagli del tenant.

  • Enter_the_Client_Secret_Here con il valore del segreto dell'app daemon client copiato in precedenza.

  • Enter_the_Web_Api_Application_Id_Here con l'ID applicazione (client) dell'app API Web copiata in precedenza.

Si noti che la proprietà scopes nella variabile protectedResources è l'identificatore di risorsa (URI ID applicazione) dell'API Web registrata in precedenza. L'URI dell'ambito completo è simile a api://Enter_the_Web_Api_Application_Id_Here/.default.

Acquisire un token di accesso

Nell'editor di codice aprire il file auth.js e quindi aggiungere il codice seguente:

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

Nel codice:

  • Preparare gli oggetti tokenRequest e apiConfig. tokenRequest contiene l'ambito per cui si richiede un token di accesso. L'ambito ha un aspetto simile a api://Enter_the_Web_Api_Application_Id_Here/.default. L'oggetto apiConfig contiene l'endpoint dell'API Web. Altre informazioni sul flusso delle credenziali client OAuth 2.0.

  • Per creare un'istanza client riservata, passare l'oggetto msalConfig al costruttore della classe ConfidentialClientApplication.

    const cca = new msal.ConfidentialClientApplication(msalConfig);
    
  • Usare quindi la funzione acquireTokenByClientCredential per acquisire un token di accesso. Questa logica viene implementata nella funzionegetToken:

    cca.acquireTokenByClientCredential(tokenRequest);
    

Dopo l’acquisizione di un token di accesso, è possibile continuare a chiamare un'API.

Chiamare un'API

Nell'editor di codice aprire il file fetch.js, quindi aggiungere il codice seguente:

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

In questo codice si effettua una chiamata all'API Web passando il token di accesso come token di connessione nell'intestazione della richiesta Authorization:

 Authorization: `Bearer ${accessToken}`

Usare il token di accesso acquisito in precedenza in Acquisire un token di accesso.

Quando l'API Web riceve la richiesta, la valuta e quindi determina che si tratta di una richiesta dell'applicazione. Se il token di accesso è valido, l'API Web restituisce i dati richiesti. In caso contrario, l'API restituisce un errore HTPP 401 Unauthorized.

Finalizzare l'app daemon

Nell'editor di codice aprire il file index.js e quindi aggiungere il codice seguente:

#!/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();

Questo codice è il punto di ingresso dell'app. Usare la libreria di analisi degli argomenti della riga di comando javaScript yargs per l’app Node.js per recuperare in modo interattivo un token di accesso e quindi chiamare l'API. Usare le funzioni getToken e callApi definite in precedenza:

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

Eseguire e testare l'app daemon e l'API

A questo punto, è possibile testare l'app daemon client e l'API Web:

  1. Seguire la procedura appresa nell’esercitazione Proteggere un'API Web ASP.NET per avviare l'API Web. L'API Web è ora pronta per gestire le richieste client. Se l'API Web non viene eseguita sulla porta 44351 come specificato nel file authConfig.js, assicurarsi di aggiornare il file authConfig.js per usare il numero di porta dell'API Web corretto.

  2. Nel terminale assicurarsi di essere nella cartella del progetto che contiene l'app daemon Node.js, ad esempio ciam-call-api-node-daemon, quindi eseguire il comando seguente:

    node . --op getToDos
    

Se l'app daemon e l'API Web vengono eseguiti correttamente, si dovrebbero ottenere i dati restituiti dalla variabile todos dell’endpoint dell’API Web, in modo simile all’array JSON seguente, nella finestra della 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'
}

Passaggio successivo