다음을 통해 공유


자습서: Node.js 디먼 애플리케이션에서 웹 API 호출

이 자습서는 OAuth(Open Authorization) 2.0 클라이언트 자격 증명 권한 부여 흐름을 사용하여 Node.js 디먼 클라이언트 앱을 준비한 다음, 웹 API 호출을 위한 액세스 토큰을 획득하도록 구성하는 방법을 보여 주는 시리즈의 마지막 부분입니다. 이 시리즈의 1부에서는 Microsoft Entra 관리 센터에 웹 API 및 디먼 앱을 등록하고 권한을 부여했습니다. 이 마지막 단계에서는 노드용 MSAL(Microsoft 인증 라이브러리)을 사용하여 Node.js 애플리케이션을 빌드하여 앱에 권한 부여를 간소화하는 방법을 보여 줍니다.

이 자습서에서는 다음을 수행합니다.

  • Visual Studio Code에서 Node.js 앱을 만든 다음, 종속성을 설치합니다.
  • Node.js 앱을 사용하도록 설정하여 웹 API를 호출하기 위한 액세스 토큰을 획득합니다.

필수 조건

Node.js 디먼 프로젝트 만들기

ciam-call-api-node-daemon과 같은 Node.js 디먼 애플리케이션을 호스팅할 폴더를 만듭니다.

  1. 터미널에서 디렉터리를 cd ciam-call-api-node-daemon과 같은 노드 디먼 앱 폴더로 변경하고 npm init -y를 실행합니다. 이 명령은 Node.js 프로젝트에 대한 기본 package.json 파일을 만듭니다. 이 명령은 Node.js 프로젝트에 대한 기본 package.json 파일을 만듭니다.

  2. 다음 프로젝트 구조를 구현하려면 추가 폴더와 파일을 만듭니다.

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

앱 종속성 업그레이드

터미널에서 다음 명령을 실행하여 axios, yargs, @azure/msal-node 패키지를 설치합니다.

npm install axios yargs @azure/msal-node   

MSAL 구성 개체 만들기

코드 편집기에서 authConfig.js 파일을 연 다음, 다음 코드를 추가합니다.

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

msalConfig 개체에는 권한 부여 흐름의 동작을 사용자 지정하는 데 사용하는 구성 옵션 집합이 포함되어 있습니다.

authConfig.js 파일에서 다음을 대체합니다.

  • Enter_the_Application_Id_Here를 이전에 등록한 디먼 앱의 애플리케이션(클라이언트) ID로.

  • Enter_the_Tenant_Subdomain_Here를 디렉터리(테넌트) 하위 도메인으로 바꿉니다. 예를 들어, 테넌트 기본 도메인이 contoso.onmicrosoft.com인 경우 contoso를 사용합니다. 테넌트 이름이 없는 경우 테넌트 세부 정보를 읽는 방법을 알아봅니다.

  • Enter_the_Client_Secret_Here를 이전에 복사한 클라이언트 디먼 앱 비밀 값으로.

  • Enter_the_Web_Api_Application_Id_Here를 이전에 복사한 웹 API 앱의 애플리케이션(클라이언트) ID로.

protectedResources 변수의 scopes 속성은 이전에 등록한 웹 API의 리소스 식별자(애플리케이션 ID URI)입니다. 전체 범위 URI는 api://Enter_the_Web_Api_Application_Id_Here/.default와 유사합니다.

액세스 토큰 획득

코드 편집기에서 auth.js 파일을 연 다음, 다음 코드를 추가합니다.

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

코드에서:

  • tokenRequestapiConfig 개체를 준비합니다. tokenRequest는 액세스 토큰을 요청하는 범위를 포함합니다. 범위는 api://Enter_the_Web_Api_Application_Id_Here/.default와 같습니다. apiConfig 개체에는 웹 API에 대한 엔드포인트가 포함됩니다. OAuth 2.0 클라이언트 자격 증명 흐름에 대해 자세히 알아봅니다.

  • msalConfig 개체를 ConfidentialClientApplication 클래스의 생성자에 전달하여 기밀 클라이언트 인스턴스를 만듭니다.

    const cca = new msal.ConfidentialClientApplication(msalConfig);
    
  • 그런 다음 acquireTokenByClientCredential 함수를 사용하여 액세스 토큰을 획득합니다. getToken 함수에서 이 논리를 구현합니다.

    cca.acquireTokenByClientCredential(tokenRequest);
    

액세스 토큰을 획득하면 API 호출을 계속할 수 있습니다.

API 호출

코드 편집기에서 auth.js 파일을 연 다음, 다음 코드를 추가합니다.

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

이 코드에서는 요청 Authorization 헤더에 전달자 토큰으로 액세스 토큰을 전달하여 웹 API를 호출합니다.

 Authorization: `Bearer ${accessToken}`

이전에 액세스 토큰 획득에서 획득한 액세스 토큰을 사용합니다.

웹 API가 요청을 수신하면 웹 API는 요청을 평가한 다음 애플리케이션 요청임을 확인합니다. 액세스 토큰이 유효한 경우 웹 API는 요청된 데이터를 반환합니다. 그렇지 않으면 API는 401 Unauthorized HTTP 오류를 반환합니다.

디먼 앱 완료

코드 편집기에서 index.js 파일을 연 다음, 다음 코드를 추가합니다.

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

이 코드는 앱의 진입점입니다. Node.js 앱에 대해 yargs JavaScript 명령줄 인수 구문 분석 라이브러리를 사용하여 액세스 토큰을 대화형으로 가져온 다음, API를 호출합니다. 앞에서 정의한 getTokencallApi 함수를 사용합니다.

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

디먼 앱 및 API 실행 및 테스트

이제 클라이언트 디먼 앱 및 웹 API를 테스트할 준비가 되었습니다.

  1. ASP.NET 웹 API 보안 자습서에서 배운 단계를 사용하여 웹 API를 시작합니다. 이제 웹 API가 클라이언트 요청을 처리할 준비가 되었습니다. authConfig.js 파일에 지정된 대로 포트 44351에서 웹 API를 실행하지 않는 경우 올바른 웹 API의 포트 번호를 사용하도록 authConfig.js 파일을 업데이트해야 합니다.

  2. 터미널에서 ciam-call-api-node-daemon과 같은 디먼 Node.js 앱이 포함된 프로젝트 폴더에 있는지 확인한 다음, 다음 명령을 실행합니다.

    node . --op getToDos
    

디먼 앱과 웹 API가 성공적으로 실행되면 콘솔 창에서 다음 JSON 배열과 유사한 웹 API 엔드포인트 todos 변수에서 반환된 데이터를 찾아야 합니다.

{
    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'
}

다음 단계