Microsoft FlowでMicrosoft Graph APIを利用する２通りの方法

#はじめに

本記事では、Microsoft FlowからMicrosoft Graph APIを利用する際の２通りの実装方法の手順を続けて紹介していきます。
また今回の手順の紹介を通じてAzure ADにおける認証プロトコル（Azure AD v2.0エンドポイント）に対する理解も深めていきます。

#方法１：アプリがユーザーに成り代わってGraph APIにアクセスする方法

###前提

- Office365アカウントを利用します。
- ここでは例として「User.Read」権限を利用してユーザー情報をGraph APIから取得するFlowを作成します。
- Azure AD、Flow、OneDrive for Businessの機能を利用します。
- Graph APIにアクセスするたびに新しいアクセストークンとリフレッシュトークンを取得します。
- 最新のリフレッシュトークンを保持するリポジトリとしてOneDrive for Businessを利用します。

また、手順を始める前にAzure AD v2.0 エンドポイントに関する以下の基本情報を頭に入れておくと捗ります。

- Azure ADは認証に成功したクライアントにアクセストークンとリフレッシュトークンを発行する。
- リソース（Graph API）アクセス時の認可に必要なアクセストークンの有効期限は短い。(標準で1時間)
- 新しいアクセストークンを取得するためにはリフレッシュトークンが必要。
- offline_access権限を利用するとリフレッシュトークンを利用して新しいアクセストークンを取得する際に同時に新しいリフレッシュトークンも取得できる。

###アプリを登録する

[アプリを Azure AD v2.0 エンドポイントに登録する](https://docs.microsoft.com/ja-jp/graph/auth-register-app-v2) を参考に [Microsoft アプリ登録ポータル](https://apps.dev.microsoft.com/portal/register-app) にてAzureADにアプリの登録を行います。設定は下記のように行ってください。

- **プロパティ** ：任意の名前を指定（今回は「testApp」）。アプリケーションIDを控えておく。
- **アプリケーション シークレット** ：[新しいパスワードを生成]をクリック。生成されるパスワードを控えておく。
- **プラットフォームの追加** ：[プラットフォームの追加]をクリックして[Web]を選択し、リダイレクト URLに「http\://localhost/myapp/」を指定。
- **Microsoft Graph のアクセス許可** ：「委任されたアクセス許可」で[追加]をクリックし、「[offline_access](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes)」を選択。（このアクセス許可が付与されなければ新しいリフレッシュトークンが発行されず、新しいアクセストークンを継続的に取得することができない）

<img width="917" src="https://qiita-image-store.s3.amazonaws.com/0/258416/795dec89-8726-339f-aa21-25f76547a857.png">

最後に[保存]をクリックして登録を完了させます。

###承認を取得する

Webブラウザで以下の同意エンドポイントURLにアクセスします。（見やすさのため改行を入れています）
＜アプリケーションID＞にはアプリ登録時に控えた値を指定します。

```
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
response_type=code&
redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F&
response_mode=query&
scope=offline_access%20user.read&
state=12345&
client_id=＜アプリケーションID＞
```

Office365アカウントでサインインし、画面表示される下記の同意エクスペリエンス画面で[承諾]をクリックします。（[全体管理者でサインインした場合は、「組織の代理として同意する」チェックボックスが表示されます](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/application-consent-experience#common-consent-scenarios)が、今回はチェックは不要です。）
![image.png](https://qiita-image-store.s3.amazonaws.com/0/258416/fd51c053-80f7-020d-5447-3426ab6036ac.png)

以下のようなlocalhostへの接続が拒否された旨のエラー画面が表示され、URL欄に表示されたリダイレクト先 localhost/myapp/ にcodeパラメータが含まれていれば承認の取得は成功です。codeパラメータの値を控えておいてください。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/258416/03d9dff6-9c0f-798d-264c-b336a5f9a836.png)


###リフレッシュトークンを取得してOneDriveに保存する

新規フローを作成し、「モバイルのflowボタン」トリガーを追加。
<img width="801" src="https://qiita-image-store.s3.amazonaws.com/0/258416/a23d900e-9e0b-da86-5e6d-1c4c072e8bf4.png">


「HTTP」アクションを追加し、以下のように設定。
・方法：POST
・URI：https\://login.microsoftonline.com/common/oauth2/v2.0/token
・ヘッダー Content-Type：application/x-www-form-urlencoded
・本文：
client_id=＜アプリ登録時に控えたアプリケーションID＞
&scope=user.read
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&grant_type=authorization_code
&client_secret=＜アプリ登録時に控えたパスワード＞
&code=＜承認取得時に控えたコードパラメータ＞

<img width="825" src="https://qiita-image-store.s3.amazonaws.com/0/258416/fb6527aa-c140-45fc-ffb0-7a26d83ef383.png">

「json の解析」アクションを追加し、以下のように設定。
・コンテンツ：@{body('HTTP')}
・スキーマ：{"type": "object","properties": {"token_type": {"type": "string"},"scope": {"type": "string"},"expires_in": {"type": "integer"},"ext_expires_in": {"type": "integer"},"access_token": {"type": "string"},"refresh_token": {"type": "string"}}}
<img width="820" src="https://qiita-image-store.s3.amazonaws.com/0/258416/e8882395-2496-f498-0d35-2d0fe2547b0b.png">

OneDrive for Businessの「ファイルの作成」アクションを追加し、以下のように設定。
・フォルダーのパス：OneDrive内の任意のパス（今回はルートパス）
・ファイル名：任意のファイル名（今回は「testApp_refreshToken.info」）
・ファイルコンテンツ：@{body('JSON_の解析')?['refresh_token']}
<img width="823" src="https://qiita-image-store.s3.amazonaws.com/0/258416/7bcfae97-2621-3b40-a289-209b31310a2b.png">


作成したFlowの全体像は以下のようになります。
<img width="840" src="https://qiita-image-store.s3.amazonaws.com/0/258416/f0f913fe-cdf6-7202-94e9-9e1a935bdf90.png">

作成したFlowを実行し、正常に実行されたことを確認します。
※このときHTTPアクションで「AADSTS70008」エラーが発生してFlowが失敗した場合は、承認取得時のコードが期限切れとなっているため、承認取得手順を再度実施し新しいコードを使用してFlowを作成してください。
<img width="824" src="https://qiita-image-store.s3.amazonaws.com/0/258416/f9e62b51-4f3c-d28e-4cd2-feaede10bca6.png">

OneDriveにリフレッシュトークンを格納できたら、ここで作成したFlowは不要のため削除して構いません。

##Graph APIを利用するFlowの作成

新規フローを作成し、「モバイルのflowボタン」トリガーを追加。
<img width="801" src="https://qiita-image-store.s3.amazonaws.com/0/258416/a23d900e-9e0b-da86-5e6d-1c4c072e8bf4.png">

OneDrive for Businessの「パスによるファイル コンテンツの取得」アクションを追加し、以下のように設定。
・フォルダーのパス：OneDriveに作成したtestApp_refreshToken.infoのパス
<img width="826" src="https://qiita-image-store.s3.amazonaws.com/0/258416/653acc4e-9237-cb6f-aaca-b4caf3d7626a.png">

「HTTP」アクションを追加し、以下のように設定。
・方法：POST
・URI：https\://login.microsoftonline.com/common/oauth2/v2.0/token
・ヘッダー Content-Type：application/x-www-form-urlencoded
・本文：
client_id=＜アプリ登録時に控えたアプリケーションID＞
&scope=offline_access%20user.read
&refresh_token=@{body('パスによるファイル_コンテンツの取得')}
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&grant_type=refresh_token
&client_secret=＜アプリ登録時に控えたパスワード＞
<img width="824" src="https://qiita-image-store.s3.amazonaws.com/0/258416/275c1346-ad8e-6181-4d7f-ea63fd4fb1c7.png">

「json の解析」アクションを追加し、以下のように設定。
・コンテンツ：@{body('HTTP')}
・スキーマ：{"type": "object","properties": {"token_type": {"type": "string"},"scope": {"type": "string"},"expires_in": {"type": "integer"},"access_token": {"type": "string"},"refresh_token": {"type": "string"}}}
<img width="825" src="https://qiita-image-store.s3.amazonaws.com/0/258416/3de84f26-d738-7a27-585f-2736b030dc63.png">

「HTTP」アクションを追加し、以下のように設定。
・方法：GET
・URI：https\://graph.microsoft.com/v1.0/me
・ヘッダー Authorization：Bearer @{body('JSON_の解析')?['access_token']}
<img width="824" src="https://qiita-image-store.s3.amazonaws.com/0/258416/31a0f0b9-e75d-92df-fbd4-6bd8cf4c1835.png">

OneDrive for Businessの「ファイルを更新します」アクションを追加し、以下のように設定。
・フォルダーのパス：OneDriveに作成したtestApp_refreshToken.infoのパス
・ファイルコンテンツ：@{body('JSON_の解析')?['refresh_token']}
<img width="823" src="https://qiita-image-store.s3.amazonaws.com/0/258416/eccb12fd-e044-90f2-85c3-7e3015e68975.png">

Flowの全体像は以下のようになります。
<img width="826" src="https://qiita-image-store.s3.amazonaws.com/0/258416/639404f2-944b-9f36-96a6-4ed3e9965852.png">


###動作確認

Flowを実行すると、HTTP２アクションの出力の本文に、アクセス許可の委任を同意したユーザーのユーザー情報が表示されます。Flowを実行するたびにOneDriveに保存されたリフレッシュトークンが更新されて、Flowを繰り返し実行できれば動作はOKです。
<img width="827" src="https://qiita-image-store.s3.amazonaws.com/0/258416/63ee80da-deab-c7e3-d568-7e375d2039a7.png">

#方法２：アプリが直接Graph APIにアクセスする方法

###前提

- Office365アカウントを利用します。
- ここでは例として「Calendar.Read」権限を利用して予定表のイベント情報をGraph APIから取得するFlowを作成します。
- Azure AD、Flow、Exchange Onlineの機能を利用します。

###アプリを登録する

方法１で利用したアプリ登録ポータルは古いポータルであり、方法２で実施する一部設定が出来ないため、次は新しいポータルとなるAzure Portalを利用してアプリを登録していきます。

[Microsoft Azure ポータル](https://portal.azure.com/) に接続して画面左のブレードメニューから[Azure Active Directory]-[アプリの登録（プレビュー）]と開き、「+ 新規登録」をクリックします。（アプリ一覧には先程登録したアプリ（testApp）が表示されています。）
<img width="960" src="https://qiita-image-store.s3.amazonaws.com/0/258416/db4adb80-0982-94f4-0bc6-f7a1da285b03.png">

「名前」で任意のアプリ名（今回はtestApp2）を指定し、「登録」をクリックします。
<img width="960" src="https://qiita-image-store.s3.amazonaws.com/0/258416/92fcb4ed-e160-38a9-e160-f9490138f853.png">

testApp2の[概要]を開いて、[アプリケーション(クライアント)ID]と[ディレクトリ(テナント)ID]の値を控えておきます。
<img width="837" src="https://qiita-image-store.s3.amazonaws.com/0/258416/a2a86c68-3a2a-36ee-fa33-5a55351e5e36.png">

[testApp2 - APIのアクセス許可]を開いて、「+ アクセス許可の追加」クリックします。
<img width="833" src="https://qiita-image-store.s3.amazonaws.com/0/258416/753a1c6b-3c01-29c7-5f3a-ed2c3c86ea54.png">

「Microsoft Graph」を選択します。
<img width="528" src="https://qiita-image-store.s3.amazonaws.com/0/258416/973d9f46-0904-8dec-3590-5427dbe58d7a.png">

「アプリケーションの許可」を選択し、利用したいアクセス許可（今回は「Calendar.Read」）をチェックして、「アクセス許可の追加」ボタンをクリックします。
<img width="570" src="https://qiita-image-store.s3.amazonaws.com/0/258416/716b1956-6aa5-3ff1-fafd-3299b2362bde.png">

[testApp2 - APIのアクセス許可]を開いて、[***に管理者の同意を与えます]をクリックし、確認ダイアログで「はい」をクリックします。
<img width="835" src="https://qiita-image-store.s3.amazonaws.com/0/258416/b71e0f78-c4e3-420b-111a-44c6c273197f.png">

[testApp2 - 証明書とシークレット]を開いて、[+ 新しいクライアントシークレット]をクリックし、追加ダイアログで[追加]をクリックします。新しく生成したシークレットの値を控えます。
<img width="817" src="https://qiita-image-store.s3.amazonaws.com/0/258416/3587fdf0-4dbd-8741-abc3-b1d7b3f7d084.png">


###Graph APIを利用するFlowの作成

新規フローを作成し、「モバイルのflowボタン」トリガーを追加。
<img width="801" src="https://qiita-image-store.s3.amazonaws.com/0/258416/a23d900e-9e0b-da86-5e6d-1c4c072e8bf4.png">

「HTTP」アクションを追加し、詳細オプションを展開して以下のように設定。
・方法：GET
・URI：https\://graph.microsoft.com/v1.0/users/＜アプリを登録したAzureAD上の任意のユーザーのUPN＞/events
・認証：Active Directory OAuth
・テナント：＜アプリ登録時に控えたディレクトリID＞
・対象ユーザー：https\://graph.microsoft.com
・クライアントID：＜アプリ登録時に控えたアプリケーションID＞
・資格情報の種類：シークレット
・シークレット：＜アプリ登録時に控えたシークレット＞
<img width="823" src="https://qiita-image-store.s3.amazonaws.com/0/258416/a6d2c5ad-9698-a3e8-1d95-9cdaf38e1ac0.png">

###動作確認

Flowを実行して、HTTPアクションの出力の本文に、先程指定したUPNのユーザーの予定表のイベント情報が表示できていれば動作はOKです。
<img width="822" src="https://qiita-image-store.s3.amazonaws.com/0/258416/11fa74f9-4849-dbd4-bad3-2fe796fdc0b2.png">

#方法１と方法２の違い

ここまで紹介した２つの方法に見られるAzure AD v2.0エンドポイントの利用の違いを自分なりに解釈して以下の通り整理してみました。（まだ理解の浅い部分も多分にあるので間違っていたらご指摘ください）

**認証フローの違い**

[一番分かりやすい OAuth の説明](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be) および [v2.0 プロトコル - OAuth 2.0 と OpenID Connect](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/active-directory-v2-protocols) を参考に、方法１，方法２それぞれをOAuth 2.0 フローに当てはめてみます。

・方法１

①②③④：「承認を取得する」手順に対応
⑤⑥：「リフレッシュトークンを取得してOneDriveに保存する」手順に対応
<img width="700" src="https://qiita-image-store.s3.amazonaws.com/0/258416/4dbbe06d-9e1c-91f8-bd1f-80bc868148dc.png">

⑦⑧⑨⑩：「動作確認」手順に対応
<img width="700" src="https://qiita-image-store.s3.amazonaws.com/0/258416/7a3934ec-5bad-3654-623f-f9e67ad03f0b.png">


補足すると、実際のサードパーティアプリでは①②と④⑤⑥のフローはWeb画面の自動遷移により自動的に行われますが、今回はFlowだけを利用しているため「承認を取得する」「リフレッシュトークンを取得してOneDriveに保存する」手順での手作業が発生しています。

・方法２

①②③④：「動作確認」手順に対応
<img width="700" src="https://qiita-image-store.s3.amazonaws.com/0/258416/54f55053-1394-8beb-ea9e-2517d8c1f91f.png">

**アプリ登録時にアプリに付与する[アクセス許可の種類](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v1-permissions-and-consent#types-of-permissions)の違い**
・方法１：[委任されたアクセス許可]により、同意エクスペリエンスによりユーザーからアクセス許可を委任されて利用できるアクセス権
・方法２：[アプリケーションのアクセス許可]により、アプリ自身が利用できるアクセス権

**アプリがリソース(Graph API)にアクセスする際の違い**
・方法１：アプリ(testApp)がアクセス許可の委任に同意したユーザーに成り代わってリフレッシュトークンを使ってAzureADに認証し、リソースにアクセス
・方法２：アプリ(testApp2)がアプリ自身としてシークレットを使ってAzureADに認証し、リソースにアクセス

**[v2.0 用のアプリケーションの種類](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-app-types)のうち該当するアプリパターン**
・方法１：[モバイル アプリとネイティブ アプリ](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-app-types#mobile-and-native-apps)
・方法２：[デーモンおよびサーバー側のアプリ](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-app-types#daemons-and-server-side-apps)

**ユーザーによる[同意の種類](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v1-permissions-and-consent#types-of-consent)の違い**
・方法１：動的なユーザーの同意（scopeパラメータを使用して承認コードに含めるアクセス許可を動的に指定できる同意のこと）
・方法２：管理者の同意（[testApp2 - APIのアクセス許可]で[***に管理者の同意を与えます]をクリックする同意のこと。手順作成後に分かったが、実は[管理者用の同意エンドポイント](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-permissions-and-consent#using-the-admin-consent-endpoint)URLを使っても同じ操作が可能）

#実務でFlowからGraph APIを使うなら方法２がおすすめ
方法２の方が構成が簡単になる以外にも以下のような理由があります。

- リフレッシュトークンの有効期限は標準で[90日間らしい](https://blogs.technet.microsoft.com/jpazureid/2018/02/28/aad-issure-token-lifetime/)ので、方法１だとFlowを3ヶ月間実行しない（リフレッシュトークンを更新しない）と「承認を取得する」「リフレッシュトークンを取得してOneDriveに保存する」手順が再度発生する。
- 方法１は委任するユーザー自身のデータに限定したアクセス権を定義できるため、一般ユーザーによる利用も可能だが、「承認を取得する」手順を一般ユーザーに行わせるのは難しい。よって管理者が管理目的のため利用するのが現実的。

#Microsoft Graph API が体系的にまとまっているサイト

公式なら [Office 365 API](https://docs.microsoft.com/ja-jp/previous-versions/office/office-365-api/) や [Microsoft Graph REST API v1.0 リファレンス](https://docs.microsoft.com/ja-jp/graph/api/overview?view=graph-rest-1.0)、
MSの中の人の記事なら [Microsoft Graph を使ってみよう](https://qiita.com/kenakamu/items/6a1ca508dc093779eefa) がおすすめです。

ただ各Graph APIに対応する権限スコープ（アプリに付与するアクセス許可）がまとめられた記事は見当たらなかったため、利用する際は各自で要デバッグです。

#おわりに

今回の方法２を利用して私もPowerApps + Flow + Graph API から成るアプリをいくつか作成して自分で運用しているので、こちらもまたの機会に紹介したいと思います。

Azure AD v2.0エンドポイント（v1.0とは微妙に[違い](https://qiita.com/kenakamu/items/3ea0a8d149eb44a9b08b#azure-ad-v1-%E3%81%A8-v2)があるようですが今回の本質ではないので無視しました）は、MS公式でもドキュメントが非常に多岐にわたっており、うかつに深入りすると相当大変であることを実感しました。当初は方法１と２の手順のみを記載したタイトルの範囲内の記事にしようとしていましたが、興味本位で認証のしくみを調べていくうちにこんな枝葉に及ぶ記事になってしまっていました。