📖

Nodejs環境でMicrosoft GraphAPIを使用する

2024/04/05に公開

NodeJS(ts-node)環境でGraphAPIを使用したデータの取得を試みる

GraphAPIを使用するには認証が必要

認証のパターンは2つ

  1. ユーザー込みで認証する
  2. アプリのみで認証する

GraphAPIにアクセスするのが、ユーザーかそれともアプリかという違い

今回はアプリのバックエンドでGraphAPIを使用したいのでアプリのみで認証する方式を採用する

認証の手順

公式ドキュメントによると

  1. アプリをMicrosoft Entra IDに登録
  2. 承認を要求
  3. アクセストークンを要求
  4. アクセストークンを使用して、Microsoft Graph を呼び出す
  5. [省略可能]更新トークンを使用して、期限切れのアクセストークンを更新

https://learn.microsoft.com/ja-jp/graph/auth-v2-user?tabs=http

まあ特段変わったところはない

アプリのセットアップ

https://learn.microsoft.com/ja-jp/graph/auth-register-app-v2

  1. Microsoft Entra管理センター

  2. ID > アプリケーション > アプリの登録

  3. 証明書とシークレット > 新しいシークレットを作成

  4. 次の値を確認する

    • クライアントID
    • テナントID
    • クライアントシークレット

実装

今回のサンプルコードは以下のリポジトリに置いておく

https://github.com/blackmax1886/graph-api-sample

Microsoft Graph SDKのインストール

npm install @microsoft/microsoft-graph-client

認証ツールのセットアップ

npm install @azure/msal-node dotenv axios
  • msal-node

    Microsoft Authentication LibraryのNode.js向けライブラリ

    MicrosoftのクラウドサービスやGraph APIへのアクセスをするための認証を行ってくれる

  • dotenv

    環境変数を扱うライブラリ

  • axios

    APIリクエストを行うためのHTTPクライアント

環境変数

.env.exampleをコピーして.envを作成する

CLIENT_ID =""
TENANT_ID=""
CLIENT_SECRET="YOUR SECRET VALUE"

Configuration

Graph APIを使用するための基本的な設定を行う

import { ClientCredentialRequest, Configuration } from "@azure/msal-node";
import 'dotenv/config';

const msalConfig: Configuration = {
    auth: {
        clientId: process.env.CLIENT_ID!,
        authority: `https://login.microsoftonline.com/${process.env.TENANT_ID}`,
        clientSecret: process.env.CLIENT_SECRET,
    }
}

const tokenRequest: ClientCredentialRequest = {
    scopes: ["https://graph.microsoft.com/.default"]
}

const apiConfig = {
    uri: "https://graph.microsoft.com/v1.0"
}

export const configuration = {
    msalConfig,
    tokenRequest,
    apiConfig
}

AccessToken取得

設定した認証情報をもとにmsalクライアントを使ってアクセストークンを取得する

import { ConfidentialClientApplication } from "@azure/msal-node";
import { configuration } from "./config";

export async function getAccessToken() {
    const msalClient = new ConfidentialClientApplication(configuration.msalConfig)
    try {
        const authResponse = await msalClient.acquireTokenByClientCredential(configuration.tokenRequest)
        return authResponse?.accessToken
    } catch (error) {
        console.log(error)
        throw new Error("Error acquireing access token")
    }
}

任意のGraph APIを使用する

import axios from 'axios';
import { getAccessToken } from './accessToken';
import { configuration } from './config';

export async function FetchUserData() {
    try {
        const accessToken = await getAccessToken();
        const response = await axios.get(`${configuration.apiConfig.uri}/users`, {
            headers: {
                Authorization: `Bearer ${accessToken}`
            }
        });
        console.log(response.data.value.length,'users');
        console.log('next link is:', response.data['@odata.nextLink']);
    } catch (error) {
        console.error(error);
    }
}

export async function FetchAllUsers(nextLink = null) {
    let users: any = [];
    let requestUrl = nextLink || `${configuration.apiConfig.uri}/users`;

    while (requestUrl) {
        const accessToken = await getAccessToken();
        const response = await axios.get(requestUrl, {
            headers: {
                Authorization: `Bearer ${accessToken}`
            }
        });
        users = users.concat(response.data.value);
        requestUrl = response.data['@odata.nextLink'];
    }
    console.log(users.length,'users')
    // return users;
}

今回は例としてUser一覧取得のAPIをリクエストしてみる。

https://learn.microsoft.com/ja-jp/graph/api/user-list?view=graph-rest-1.0

リクエストテスト

import { FetchAllUsers } from "./graph-api/fetchUserData";

const main = () => {
    FetchAllUsers().catch(console.error);
}

main()
npx ts-node main.ts

これでユーザーの一覧が取得できるのが確認できる

User APIのページング

FetchUserDataを叩いてみると1度に100件しかユーザーが取得できなかった。
このAPIはページングという仕様で大きすぎるデータを分割して送信している

同じレスポンス内に@odata.nextLinkプロパティがある

requestUrl = response.data['@odata.nextLink'];

これは次のページ分のユーザーデータをリクエストできるURIを示す
FetchAllUserではこの@odata.nextLinkを使って全てのユーザーを取得している

Discussion