TeamsFx SDK で Microsoft Graph からTeams データを取得しよう

## 😘 TL;DR

まずはじめに。このチュートリアルは私が最初に意図したよりもずっと長くなってしまったので、記事の要約を先に書いておきますね。

- チームアプリ開発のための様々なツールとSDKがあるので必要なツールをまず選ぶのが大事
- TeamsFx は、基本的なユーザー情報などのデータを取得するために Microsoft Graph API を使用するアプリを構築するときに最適なツール
- TeamsFx は OAuth 2.0 認証コード付与フローを処理し、アクセス トークンを取得、そのトークンを使用して Graph API 呼び出しを行う
- Graph API のラッパーメソッドの `client.api(path).get` を使用して呼び出しを行う

では、メインコンテンツに進みましょう。
![teamsfx-cover-1000x420.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663749/f02f3f2c-43ef-93d0-cffe-c307d6d55910.png)

[前回のチュートリアル](https://qiita.com/girlie_mac/items/a6f8afc7b6b955e018e0)では、VS Code 拡張機能である Teams Toolkit のインストール、 そしてそのツールを使っての Teams ボットの骨組みの構築をしました。このチュートリアルでは、Toolkit のサンプル ボットのコードをざっと解説しつつ、新たに Microsoft Graph（マイクロソフト グラフ）と TeamsFx（チームズ Fx フレームワーク）を紹介していきます。

まず、コードに入る前に、開発ツールについて簡単に説明します。

## 🔧 Teams アプリ開発ツール

今まで書いてきた Microsoft Teams 関連のチュートリアルでは、Tabs コアユーザーインターフェイスを構築するための **Teams SDK**、ボットとメッセージング拡張機能を構築するための **Bot Framework**、メッセージ内の UI コンポーネント用**アダプティブカード**、およびすべてのタイプのチームアプリを素早くスキャフォールディングするための **Teams Toolkit** VSCode拡張機能について言及しました。

一部のツールは、Teams アプリの開発用だけでなく複数のマイクロソフトのプラットフォームにまたがって使用するように作成されています。では、Teams で使用可能なツールはこんな感じです。

| プロダクト | 種類 | Teams アプリ開発においての役割 | Teams 以外での役割 |
| --- | --- | --- | --- |
| [Teams SDK](https://docs.microsoft.com/javascript/api/overview/msteams-client?view=msteams-client-js-latest&WT.mc_id=m365-37637-timura)(Teams.js)| Teams クライアント SDK | タブ開発 (フロントエンド) | (Teams 専用ツール) |
| [Bot Framework](https://dev.botframework.com/?WT.mc_id=m365-37637-timura) | Bot SDK | Teams ボット開発|ウェブチャット&モバイルチャット、Skype、Facebook、Amazon Alexa、Slack、Twilio など|
|[アダプティブ カード](https://adaptivecards.io/?WT.mc_id=m365-37637-timura) |プラットフォームに依存しない UI スニペット (JSON) |リッチフォーマットのメッセージとカード |ウェブおよびモバイル UI、Outlook メッセージなど|
|[App Studio](https://docs.microsoft.com/microsoftteams/platform/concepts/build-and-test/app-studio-overview/?WT.mc_id=m365-37637-timura)|ビジュアル ツール| Teams アプリ パッケージの作成と構成 |(Teams 専用ツール) |
|[Teams Toolkit ](https://marketplace.visualstudio.com/items?itemName=TeamsDevApp.ms-teams-vscode-extension&WT.mc_id=m365-37637-timura)| VS Code 拡張 |アプリの登録、スキャフォールドなど |(Teams 専用ツール) |
|[TeamsFx](https://docs.microsoft.com/javascript/api/@microsoft/teamsfx/?view=msteams-client-js-latest&WT.mc_id=m365-37637-timura) | SDK と CLI | アイデンティティおよび、Graph リソースへのとアクセスを実装 | (Teams 専用ツール) |
|[Microsoft Graph](https://docs.microsoft.com/graph/?WT.mc_id=m365-37637-timura) |REST API |チームを含む、Microsoft 365 サービス全体に保存されているデータへのアクセスを提供。| カレンダー、メール、OneDrive などにアクセスして管理 |
|[Microsoft Teams 開発者ポータル](https://dev.teams.microsoft.com/home/?WT.mc_id=m365-37637-timura)(プレビュー)|Web ツール | アプリ設定、リファレンス |(Teams 専用ツール) |

*Teams の開発に使用できるツールはまだありますが、このチュートリアルでは全てをカバーしていません*

通常、開発時にはこれらのツールや SDK を組み合わせを使用します。今回のこのチュートリアルでは、Teams Toolkit、Bot Framework (アダプティブ カードのラッパー メソッドを含む)、および  TeamsFx  (グラフ API ラッパーを含む) を使用します。 

ではこれから、[前のチュートリアル](https://qiita.com/girlie_mac/items/a6f8afc7b6b955e018e0)で終えたところから始めましょう。

---

## 🤖 Microsoft Bot Framework を使用する

Toolkit からテンプレート コードをビルドして実行した後、ボット コードが格納されている **bot** ディレクトリを見てみましょう。

![スクリーンショット - VS Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ngd7tbfbq2y3ra1iq7d.png)

それでは、テンプレートコードの内容をざっくりとチェックしていきましょう。

### 📄 bot/index.js

**index.js**では、[botbuilder](https://docs.microsoft.com/javascript/api/botbuilder/?view=botbuilder-ts-latest&WT.mc_id=m365-35902-timura)（マイクロソフト ボット フレームワーク）がインポートされます。

このアダプターは、ボットがユーザーと通信して応答を送信できるように作成されます。

```js
const { BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } = require("botbuilder");
...
const adapter = new BotFrameworkAdapter({
  appId: process.env.BOT_ID,
  appPassword: process.env.BOT_PASSWORD,
});
```

＊ *Teams Toolkit でのプロセスで既に Azure Active Directory にボットの登録を処理しているので、ボット ID とパスワードを手動で構成する必要はありません* 🙌

また、`Restify` は HTTP サーバーを設定や、HTTP リクエストのをルーティングをします。

```js
const server = restify.createServer();
server.listen(process.env.port);

// Listen for incoming requests.
server.post("/api/messages", async (req, res) => {
  await adapter.processActivity(req, res, async (context) => {
    await bot.run(context);
  })...
});
```

ここでは、チュートリアルを簡略化するために、状態（ステート）の管理方法や、*turn* についての説明は割愛しました。ボットの概念を学びたい場合は [ボットの仕組み](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0&WT.mc_id=m365-37637-timura)をお読みください。

### 📄 bot/teamBot.js

**bot/teamBot.js** はボットの主要なエントリポイントです。

`TeamsBot` クラスはここで作成されています。その `run` 関数は、アダプターによって呼び出され、(**index.js** からの) `Restify` ミドルウェアを介してボットのアクティビティロジックにルーティングされます。 

コンストラクタでは、ボットの動作とメッセージ テキストをカスタマイズするために [`TeamsActivityHandler`](https://docs.microsoft.com/javascript/api/botbuilder/teamsactivityhandler?view=botbuilder-ts-latest&WT.mc_id=m365-37637-timura) にあるメソッドの一部をオーバーライドし拡張しています。


```js
class TeamsBot extends TeamsActivityHandler {
  ...
  constructor(conversationState, userState, dialog) {
    super();
    ...

    this.onMessage(async (context, next) => {
      ...
    });

    this.onMembersAdded(async (context, next) => {
      ...
    });

  }
}
```
オーバーライドされたメソッドは、このサンプルでは `onMessage` と `onMembersAdded` になります。各イベントハンドラは、すべての着信メッセージアクティビティに対して出力される `message` イベントと、新しいメンバーが会話に追加されたときに生成される `MembersAdded` イベントを登録します。

#### メッセージ イベント

ユーザーからボットにメッセージが送信されると ('show' コマンドなど)、`onMessage` がトリガーされます。

```js
this.onMessage(async (context, next) => {
  await this.dialog.run(context, this.dialogState);
  await next();
});
```

次に、新しいメッセージ Activity を使用して `Dialog` を実行します。

### 📄 bot/dialogs/mainDialogs.js

```js
const { Dialog, DialogSet, DialogTurnStatus, WaterfallDialog } = require("botbuilder-dialogs");
```

[dialogs library](https://docs.microsoft.com/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0&WT.mc_id=m365-37637-timura)💬は、ユーザーとの会話のやりとりを管理するための状態ベースのモデルを提供します。ダイアログは会話型スレッドを表すタスクを実行します。


---

さて、コードの一部の動作をさらっと説明してみました。さて、これから今回のメインである、**TeamsFx** と **Graph** にアクセスしてみましょう。

## 🪅 TeamsFx とは?

TeamsFx は、アイデンティティや、**Microsoft Graph API** のアクセスを統合したもので、これらの実装を容易にするために作成されたフレームワークです。たとえば、OAuth 2.0 認証コード付与フローを処理し、アクセス トークンを取得し、そのトークンを使用して Graph API 呼び出しを行います。

### Microsoft Graph API

ではその先ほどから何度もでてくる [Microsoft Graph API](https://docs.microsoft.com/graph/?WT.mc_id=m365-37637-timura) とはいったい何なのでしょう？これは、Microsoft 365 サービスからデータを接続することができる REST API です。

![マイクロソフトグラフAPI](https://docs.microsoft.com/graph/images/microsoft-graph.png)

M365 プラットフォームは、Teams、Outlook、カレンダーなど、マイクロソフトのクラウド サービス全体で、さまざまな人中心のデータとインサイトを保持しています。Teams アプリ内で、それらのデータが必要な場合は Graph を使用してアクセスする必要があります。 

たとえば、このサンプル アプリでは、ユーザーがボットにユーザーの情報を表示するように求めると、アプリは Graph からデータを取得する API 呼び出しを行います。

では Graph から Teams ユーザに関する情報を呼び出してみましょう。

## 🪅 TeamsFx を使用してグラフ API を呼び出す

まず、**bot/dialogs/mainDialogs.js**では、TeamsFx と Graph の両方のライブラリがインポートされます。

```js
const {createMicrosoftGraphClient, loadConfiguration, OnBehalfOfUserCredential, TeamsBotSsoPrompt} = require("@microsoft/teamsfx");
const { ResponseType } = require("@microsoft/microsoft-graph-client");
```

### 🔏 ユーザー認証と承認

アプリは、`loadConfiguration()` を呼び出すことによって `MicrosoftGraphClient` を作成して認証します。 

次に、名前付きダイアログとして `TeamsBotSsoPrompt` の新しいインスタンスが追加されます。`TeamsBotSsoPrompt` は、ボットの認証プロセスを簡素化するためにボットフレームワークと統合されています。

```js
loadConfiguration();
dialogs.add(
  new TeamsBotSsoPrompt("TeamsBotSsoPrompt", {
    scopes: ["User.Read"],
  })
);
```

この、`scopes` は、Teams 内で Graph API を呼び出すために必要なアクセス許可スコープです。リソース固有のアクセス許可は細かく定義され、特定のリソース内でアプリケーションが実行できる操作を定義します。

「読み取り」または「書き込み(作成、編集、削除を含む)」が可能なさまざまなアクセス許可スコープがあります。たとえば、名前が示すように、ユーザー情報を読み取るために必要なスコープは `User.Read` です。また、アクセス許可を有効にするには、アプリはユーザーに同意を求める必要があります。

この操作で自動的にサインイン用のプロンプトが生成され、そこでボットの Teams シングル サインオン (SSO) サポートを利用して、ユーザーがサインインすることによってアプリ側が OAuth トークン 🏅 を受け取ることになります。

![Screenshot - permissions dialog](https://res.cloudinary.com/practicaldev/image/fetch/s--h3bHmAbq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4r80t4cvr4fdxt9vyx3k.png)

認証と承認はソフトウェア構築の上で重要かつ煩雑なトピックです。しかしマイクロソフトのアイデンティティ プラットフォームのみについてだけでも説明をしていくとこのチュートリアルがどんどん長くなってしまうのでここでは説明は省きます。詳しくは [Microsoft Graph の認証と承認の基本](https://docs.microsoft.com/graph/auth/auth-concepts/?WT.mc_id=m365-37637-timura)を読んでください。🙇‍♀️

### 📇 Graph API の呼び出し

アプリでは、ユーザーがボットに "show" コマンドを送信すると、アプリはGraph から API を呼び出してユーザー情報を取得します。(アプリは、前のセクションで説明したように、ユーザーの情報を取得する前に、SSO ウィンドウをポップアップしてユーザーに許可を求めます。）

すべての API 呼び出しでは、SSO サインイン プロセスで取得されたアクセス トークン 🏅が必要です。(トークンは、要求の承認ヘッダーに添付されます)。

また、Teams 用の Graph API にアクセスするには、Graph client オブジェクトを作成する認証を行うチームの認証情報が必要です。

TeamsFx での認証を簡素化するための認証情報クラスが３つあります:

- `TeamsUserCredential` - Teams の現在のユーザーのアイデンティティ。この認証情報を使用すると、最初にユーザーの同意が要求されます。 
- `M365TenantCredential` - マイクロソフト 365 テナント アイデンティティ。これは通常、ユーザーが時間トリガー自動化ジョブのように関与していない場合に使用されます。 
- `OnBehalfOfUserCredential` - 代理フロー。アクセストークンが必要で、異なるスコープには新しいトークンを取得できます。 

このボットのシナリオでは `OnBehalfOfUserCredential` を使用しています。

```js
const oboCredential = new OnBehalfOfUserCredential(tokenResponse.ssoToken);
const graphClient = createMicrosoftGraphClient(oboCredential, ["User.Read"]);
```

これで認証のセットアップと Graph クライアントのインスタンスを使用して、最終的にサービスへの呼び出しを開始できます。 

そして、ユーザー情報を取得し Teams クライアントにメッセージを送信するボットを作成する基本的な方法がこのようになります。

```js
const me = await graphClient.api("/me").get();

if (me) {
  await stepContext.context.sendActivity(
  `You're logged in as ${me.displayName} and your job title is: ${me.jobTitle}`
  );
 ...
```

さて、今回これまで学んだことの流れをフロー図でまとめるとこんな感じになります。

![Teams bot flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x77ebyoyygnxmij1cuh2.png)

では、ボットを Teams クライアントで動かしてみましょう。まだ実行していない場合は前回のチュートリアルの[サンプルボットをローカルで実行する](https://qiita.com/girlie_mac/items/a6f8afc7b6b955e018e0#-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%83%9C%E3%83%83%E3%83%88%E3%82%92%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%81%A7%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B) を参照。'show' コマンドを試して、ボットに情報を表示するよう依頼してみましょう。 

うまくいくと、このようになります
![Screenshot - bot showing user info](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjn7n2z1dy9vx6557lvu.png)
わーい 🎉

## 🧺 もっと Graph API を試してみる

Graph API を呼び出す TeamsFx の `api` メソッドを使用して、いろいろな Graph 呼び出しを行ってみましょう。すべてのリクエストは `client.api(path)` で始まり、アクションの `get` または `post` で終わります。

たとえば、ユーザーが参加している Teams のグループを取得するには、

```js
const profile = await graphClient.api("me/joinedTeams").get();
```

応答によって、グループ ID を含むグループの一覧が表示されます。

Teams グループのメンバーを取得するにはこのようします。

```js
const profile = await graphClient.api("groups/{Teams group ID}/members").get;
```

また、REST APIをテストして応答を視覚化するツール [**Graph エクスプローラ**](https://developer.microsoft.com/graph/graph-explorer/?WT.mc_id=m365-37637-timura)もお試しください。
![Screenshot - Graph Explorer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2rd4gan1mhpdmm4jzuyx.png)


---

いかがでしたか。ボット、OAuth、ID管理などの概念など知っておくべきことがたくさんありますが、情報をあふれさせることでこのチュートリアルを複雑にしたくなかったので、意図的に詳細に深く掘り下げないようにしてすべてを簡素化してました。

もっと深く学びたい方は、以下のドキュメントとコードリポジトリをご覧ください 💁‍♀️

## 📚 もっと学ぶためのリソース

- [Microsoft Bot Frameworks](https://dev.botframework.com/?T.mc_id=m365-37637-timura)
- Bot Services Documentation - [ボットのしくみ](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0&tabs=javascript&WT.mc_id=m365-37637-timura)
- [TeamsFx SDK レファレンス](https://docs.microsoft.com/javascript/api/@microsoft/teamsfx/?view=msteams-client-js-latest&WT.mc_id=m365-37637-timura)
- [TeamsFx GitHub リポジトリ](https://github.com/OfficeDev/TeamsFx)
- [Microsoft Graph API](https://docs.microsoft.com/graph/?WT.mc_id=m365-37637-timura)
- [Microsoft Graph の認証と承認の基本](https://docs.microsoft.com/graph/auth/auth-concepts/?WT.mc_id=m365-37637-timura)

---
🇺🇸 *この記事は先に英語で書いたものを和訳しています。原文（英語）で読みたいという方は [dev.to のリンク](https://dev.to/azure/fetching-teams-user-info-from-microsoft-graph-with-teamsfx-sdk-301c)からどうぞ！*