🌩

Azure Functions から Microsoft Graph API を利用する

2023/04/07に公開

Azure Functions から Microsoft Graph API を呼び出すといったことを何度か試したのですが、IDやパーミッション周りで毎回嵌るので、記事として残しておきます。

今回作成するアプリケーション

Azure Functions の HTTPトリガーで動作する関数を作成します。利用言語はC#です。
関数では Microsoft Graph API を利用して、ユーザ一覧を取得し、レスポンスとして返却します。

Microsoft Graph API の利用に認証が必要ですが、Microsoft ID プラットフォームにアプリを登録してシークレットを払い出して認証を行います。

プロジェクトの作成

下記を参考にプロジェクトを作成します。
今回はVisual Studio Codeで作りました。(Visual Studioが使えるならば、Visual Studioの方が準備の手間が少なくて済みます)

プロジェクトの作成でデフォルトを選んでいくと、HTTPトリガーによるプロジェクトが作成されます。

HttpTrigger1.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Company.Function
{
    public static class HttpTrigger1
    {
        [FunctionName("HttpTrigger1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            return new OkObjectResult(responseMessage);
        }
    }
}

Run and Debug から実行すると、ローカルで関数を実行することができます。

Graph API の呼び出し

Graph SDK を使うことで、Graph API を簡単に呼び出せます。

NuGetでMicrosoft.GraphAzure.Identityをインストールします。
Azure.Identityは、認証のために利用します。

Credentialを作成し、作成したCredentialGraphServiceClientを生成します。

var clientSecretCredential = new ClientSecretCredential(
    tenantId, clientId, clientSecret);

var graphClient = new GraphServiceClient(clientSecretCredential);

あとはGraphServiceClientからリソース名を指定して取得します。

var result = await graphClient.Users.GetAsync();

今回はユーザ一覧を取得していますが、Graph APIでは様々な操作が行えます。

Graph API を呼び出すようにした関数のコード全体は下記の通りです。

HttpTrigger1.cs
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Graph;
using System.Text;
using Azure.Identity;

namespace Company.Function
{
    public static class HttpTrigger1
    {
        [FunctionName("HttpTrigger1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var tenantId = Environment.GetEnvironmentVariable("TENANT_ID");
            var clientId = Environment.GetEnvironmentVariable("CLIENT_ID");
            var clientSecret = Environment.GetEnvironmentVariable("CLIENT_SECRET");

            var clientSecretCredential = new ClientSecretCredential(
                tenantId, clientId, clientSecret);

            var graphClient = new GraphServiceClient(clientSecretCredential);

            var result = await graphClient.Users.GetAsync();

            StringBuilder stringBuilder = new StringBuilder();
            foreach(var user in result.Value) {
                stringBuilder.AppendLine($"{user.Mail}: {user.DisplayName}");
            }

            return new OkObjectResult(stringBuilder.ToString());
        }
    }
}

Graph API の認証について

今回は、Microsoft ID プラットフォームにアプリを登録し、クライアントシークレットを利用することで認証するようにしています。

クライアントシークレット発行までの流れは下記の通りです。

(1) アプリの登録

「Azure Active Directory」→「アプリの登録」でアプリを登録します。
今回はAzure Functionsから利用するので、アカウントの種類は「この組織ディレクトリのみに含まれるアカウント (XXXX のみ - シングル テナント)」を選びます。

リダイレクトさせるような利用はしないのいで、リダイレクトURIは省略します。

(2) APIのアクセス許可

登録したアプリに対して、APIのアクセス許可を設定します。
「API のアクセス許可」から、「アクセス許可の追加」で「Microsoft Graph」を選びます。

「アプリケーションに必要なアクセス許可の種類」は「アプリケーションの許可」を選び、利用するAPIに必要な権限を追加します。
今回はユーザ一覧を参照したいので、User.Read.Allを指定しました。

管理者の同意が必要になるので、管理者の同意を与えます。(自分が管理者でない場合は、管理者に依頼します)

Graph APIで、アクセス許可として何が必要になるかは、下記が参考になります。

(3) クライアントシークレットの発行

「証明書とシークレット」から、クライアントシークレットを作成します。

作成したシークレットの値は、1度しか表示されない(移動して戻ると確認できない)ので、ここでメモします。

認証時に必要になるのは、このシークレットの値と、クライアントID、テナントIDです。
クライアントIDとテナントIDは概要から確認できます。

認証情報の管理について

先ほど下記の3つが認証に必要と書きましたが、

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

これらをソースコードにそのまま記載するのは望ましくないので、環境変数として取得するようにしています。

var tenantId = Environment.GetEnvironmentVariable("TENANT_ID");
var clientId = Environment.GetEnvironmentVariable("CLIENT_ID");
var clientSecret = Environment.GetEnvironmentVariable("CLIENT_SECRET");

var clientSecretCredential = new ClientSecretCredential(
    tenantId, clientId, clientSecret);

ローカルで実行する場合には、local.settings.jsonに環境変数を設定しておきます。

local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "TENANT_ID": "aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb",
    "CLIENT_ID": "12345678-9012-3456-7890-123456789012",
    "CLIENT_SECRET": "fffff~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdegfh"
  }
}

Azure Functions上で実行する場合には、「関数アプリ」→「構成」→「アプリケーション設定」で設定します。

Discussion