Microsoft Graphを使ってTeamsメッセージを取得する

# はじめに
データ解析をしようと思ったときによくあるデータの収集方法の例として、準備されているサンプルデータセットを利用したり、APIを使ってデータを要求することが挙げられます。

https://learn.microsoft.com/ja-jp/azure/databricks/dbfs/databricks-datasets

しかし、上記以外で解析できたら面白いデータはないかと考えたとき、`Microsoft Teams`で送信されたメッセージを解析できたら面白いと感じたことが、`Teams`メッセージを取得しようと思ったきっかけです。そこで、`Teams`メッセージを取得する方法を調べていると、`Microsoft Graph`を使う必要があるということで、本記事では`Microsoft Graph`を使って`Teams`メッセージを取得する方法をまとめます。

# Microsoft Graphとは
`Microsoft Graph`は、`Microsoft 365`サービス全体に保存されているデータへのアクセスを提供する`REST API`です。`Microsoft Graph API`を使用することで、`Office 365`、`OneDrive`、`Teams`、`Outlook`、`Window10`、などの`Microsoft 365`のコアサービスのデータにアクセスすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/1274dddb-2f66-f62d-4c77-d113e77e8132.png)

https://learn.microsoft.com/ja-jp/graph/overview

:::note
**Microsoft GraphとMicrosoft Graph APIの違い**
Microsoft GraphはAPIの集合体であり、Microsoft Graph APIはその中の1つのAPIです。
:::

## Microsft Graph APIのアクセス許可
`Microsoft Graph API`には、アプリケーションがMicrosoftのクラウドサービスにアクセスするために必要な以下の2種類のアクセス許可があります。
- 委任されたアクセス
- アプリ専用アクセス

本記事では、委任されたアクセスで`Microsoft Graph`にアクセスし、`Teams`メッセージの取得を行うことにします。

### 委任されたアクセス
委任されたアクセスは、ユーザーがクライアントアプリケーションにサインインし、クライアントアプリケーションがユーザーの代わりに`Microsoft Graph`を呼び出します。委任されたアクセスでは、ユーザーとクライアントの両方が要求を行う権限を持っている必要があります。

:::note
委任されたアクセス許可には、スコープと呼ばれるものがあります。スコープはAPIへのアクセスを制御するために使用され、APIへのアクセスを許可する範囲を指定することができます。例えば、`Microsoft Graph API`では、`User.Read`というスコープがあり、このスコープを使用することでユーザー情報を読み取ることができます。
:::

### アプリ専用アクセス
アプリ専用アクセスでは、ユーザーの代わりにアプリケーションが`Microsoft Graph API`にアクセスできます。アプリはサインインしているユーザーなしで独自にデータを操作でき、バックグラウンドサービスまたはデーモンとして主に自動化やバックアップなどのシナリオで使用されます。

:::note alert
アプリ専用アクセスを使用する場合、ユーザーの同意なしで`Microsoft Graph API`にアクセスできるため、注意が必要です。
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/3aaaa877-9e76-e80b-91dc-6bd96a629d93.png)

https://learn.microsoft.com/ja-jp/graph/auth/auth-concepts

# Teamsメッセージを取得する流れ
`Teams`に保存されているメッセージにアクセスするには、ユーザーの認証と認可が必要です。

:::note info
**認証と認可について**
認証はユーザーが自分自身であることを証明することであり、システムにログインするために必要なものです。一方で、認可はユーザーがアクセスできるリソースを決定することであり、リソースにアクセスするために必要なものです。例えば、ユーザーがパソコンにログインする際にIDとパスワードを入力することで、ユーザーが誰であるかを確認するのが認証です。その後、ユーザーが持つ権限に応じてファイルの閲覧や編集などの操作が可能かどうかを判断するのが認可です。
:::

委任されたアクセスでは、`OAuth2.0`の認可フローに基づいてユーザーの認証・認可が行われます。`OAuth2.0`では、まず初めにサインインをしてユーザー認証を行い、その後にアクセストークンを要求・取得することで認可が行われます。

:::note info
OAuth2.0とは、ユーザー権限の認可を行うためのプロトコルです。
:::

以下に、`Teams`メッセージを取得するフローをまとめます。

1. 認証フロー
    1. サインイン
    1. 認証コードの取得
1. 認可フロー
    1. アクセストークンの要求
    1. アクセストークンの取得
1. Microsoft Graphへのアクセス
    1. アクセストークンを使ってHTTPリクエスト
    1. HTTPレスポンス

:::note info
**認証コードとアクセストークンについて**
認証コードは、OAuth 2.0の認証フローで使用されるトークンの1つであり、アプリがユーザーのアクセス許可を取得するために使用されます。認証コードでは、アプリがユーザーに代わってリソースにアクセスすることはできません。一方で、アクセストークンは、OAuth 2.0の認可フローで使用されるトークンの1つであり、アプリがユーザーの代わりにリソースにアクセスするために使用されます。アクセストークンには、有効期限があります。
:::

## OAuth2.0の認可フロー
先述した`Teams`メッセージを取得するフローは、`OAuth2.0`の認可フローに基づいています。以下は`JavaScript`で書かれた`SPA(Single Page Application)`が`Microsoft identity platform`(`Azure AD`など)に認証コードとアクセストークンを要求・取得した後、`Microsoft Graph API`に対して`HTTP`リクエストを要求している図になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/891de3f3-2fa2-cf28-3321-bac3f0f66c2d.png)

https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-auth-code

# Microsoft Graphを使用するための下準備
ここまで、`Microsoft Graph`の説明や、`Teams`メッセージを取得する流れを`OAuth2.0`の認可フローを元に説明しました。ここからは実際に、`Microsoft Graph`を使用するための準備事項について説明していきます。

## API呼び出しのアクセス許可の種類とスコープの確認
本記事では、`Teams`メッセージを取得する必要があります。[公式ドキュメント](https://learn.microsoft.com/ja-jp/graph/api/channel-list-messages?view=graph-rest-1.0&tabs=http)によると、`Teams`メッセージにアクセスするための許可の種類として、委任(職場または学校のアカウント)、委任(個人用Microsoftアカウント)、アプリケーションの3種類がありますが、委任(個人用Microsoftアカウント)はアクセスがサポートされていないことがわかります。今回はアプリ専用アクセスではなく、委任されたアクセスで`Microsoft Graph API`を使用するため、委任(職場または学校アカウント)でアクセスすることになります。このとき、必要になるスコープは`ChannelMessage.Read.All`であるとわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/9735ca60-e760-fd1f-6372-075a55525d1c.png)

## Microsoft 365テナントの作成
自身で管理している職場または学校用のアカウントを持っていない場合、委任(職場または学校のアカウント)でアクセスするためにはMicrosoft 365テナントを作成し、自身がテナント管理者になる必要があります。[Microsoft 365 開発者プログラム](https://developer.microsoft.com/en-us/microsoft-365/dev-program)に参加することで、無料でMicrosoft 365 E5開発者サンドボックスサブスクリプションのセットアップができ、90日間だけ有効のテナントの管理者になることができます。

https://learn.microsoft.com/ja-jp/training/modules/what-is-m365/5-explore-microsoft-365-tenant

登録すると、以下のようなダッシュボードが表示されます。
![f77fac13-0f8d-e9d4-228e-4762736b5388.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/9f816cfb-75ec-ed95-ab91-725238c60cc4.png)

## アプリケーションの登録
Azureポータルにログインし、`Azure Active Directory`>`アプリの登録`>`＋新規登録`から任意のアプリ名でアプリを登録してください。本記事では、`TeamsAPIApp`としました。`TeamsAPIApp`の概要ページでは、クライアントIDや、テナントID、クライアントシークレット、認証後のリダイレクトURI等の情報が表示されています。
![スクリーンショット 2023-05-21 155352.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/a2c93f0a-87c0-845f-52e1-c2c590e15ac8.png)

次に、`APIのアクセス許可`>`+アクセス許可の追加`>`Microsoft Graph`>`委任されたアクセス許可`>`ChannelMessage.Read.All`を選択して、`アクセス許可の追加`のボタンを押下します。その後、`MSFTに管理者の同意を与えます`をクリックします。最後に、以下のスクリーンショットのように、`ChannelMessage.Read.All`が`MSFTに付与されました`という状態になればアプリの登録は完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/c15845f8-520c-b087-6eed-e8e5d2e5cd18.png)

# アプリケーションのコーディングと実行
ここでようやく、アプリケーションのコーディング及び実行を行っていくことができるようになりました。ここでは本記事の要点から外れるために説明を省略しますが、今回は`JavaScript`で記述した`SPA`を作成しています。また、`node.js`でWebアプリケーションを作成するためにフレームワークである、`express`を使いました。

## 認証フロー
まずは、認証するためにアクセスするエンドポイントを指定します。IDやリダイレクト先URI等の情報は、Azureポータルのアプリの登録にある`TeamsAPIApp`から確認することができます。
```javascript:endpoint.js
const endpoint = "https://login.microsoftonline.com/<テナントID>/oauth2/v2.0/authorize" + 
"?client_id=<クライアントアプリケーションのID>" + 
"&response_type=code" + 
"&redirect_uri=<認証コードを受け取るためのリダイレクト先のURI>" +
"&response_mode=query" +
"&scope=<アクセストークンでアクセスしたいリソースの範囲>" +
"&state=<クライアントアプリケーションが任意に指定できるパラメータ>";
```
上で示したエンドポイントにアクセスすると、以下のようなサインインの画面が表示されます。
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/76ad4b74-bba3-2a91-d075-5fd21653f533.png" width = 50%>

::: note info
サインインは、ユーザーの認証を行うプロセスです。ユーザーが自分自身であることを証明するために、IDとパスワードなどの資格情報を使用します。
:::

サインインに成功すると認証コードが返され、Azureポータルと`redirect_uri=<認証コードを受け取るためのリダイレクト先のURI>`でリダイレクト先として設定したURLに飛ばされます。Azureポータルでは、`http://localhost/accesstoken`と設定しています。

![スクリーンショット 2023-05-21 162912.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/b24e45de-7211-566a-f4d2-d71a3a3f339c.png)

:::note alert
Azureポータルと`redirect_uri=<認証コードを受け取るためのリダイレクト先のURI>`で指定したURLが異なると、以下のようなエラーになります。
![スクリーンショット 2023-05-18 135101.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/329b9713-3684-cb2e-64d7-479c648f56b4.png)
:::

## 認可フロー
これまでで認証コードを取得できているため、次は認可フローを経てアクセストークンを取得していきます。HTTPリクエストを簡単に行えるライブラリである`axios`は`const axios = require('axios');`として、別箇所にて読み込まれています。以下の`JavaScript`関数によって、アクセストークンを取得することができます。
```javascript:token.js
async function getAccessToken(code, axios) {
  const options = {
    method: 'POST',
    url: "https://login.microsoftonline.com/<テナントID>/oauth2/v2.0/token",
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    data: {
      client_id: <クライアントID>,
      scope: <スコープ>,
      code: code,
      redirect_uri: <アクセストークンを受け取るためのリダイレクト先のURL>,
      grant_type: 'authorization_code',
      client_secret: <クライアントアプリケーションの秘密鍵>,
    }
  };
  return await axios(options)
  // レスポンスが正常に受信された場合
  .then((response) => {
    return response.data.access_token;
  })
  // レスポンスがエラーになった場合
  .catch((error) => {
    console.error(error);
    return undefined;
  });
}
```
ここで、取得したアクセストークンはブラウザの`Cookie`に保存しました。

## Microsoft Graphへのアクセス
ここまでで、アクセストークンを取得することができました。アクセストークンは`Cookie`に保存されているため、`Microsoft Graph`へアクセスする際は`Cookie`から毎回アクセストークンを取得します。以下の`JavaScript`関数は、`endpoint`で指定された`Microsoft Graph API`に対して、HTTPヘッダーに`Authorization:<Bearer token>`を設定した上で、GETリクエストを送信する関数です。
```javascript:msgraph.js
async function callMSGraph(endpoint, token, axios) {
    const options = {
    method: 'GET',
    url: endpoint,
        headers: {
            Authorization: token
        }
    };
    return await axios(options)
    // レスポンスが正常に受信された場合
    .then((response) => {
        return response.data;
    })
    // レスポンスがエラーになった場合
    .catch((error) => {
        console.error(error);
        return error;
    });
}
```
今回のように、`Teams`メッセージを取得するための`endpoint`は`https://graph.microsoft.com/v1.0/teams/${teamId}/channels/${channelId}/messages`です。
```:HTTP
GET /teams/{team-id}/channels/{channel-id}/messages
```
Teamsチャネルメッセージを一覧表示するMicrosoft Graphの公式ドキュメントはこちらの記事になります。

https://learn.microsoft.com/ja-jp/graph/api/channel-list-messages?view=graph-rest-1.0&tabs=http

上記のエンドポイントに対してGETリクエストを送信すると、以下のようなレスポンスが返ってきました。
![スクリーンショット 2023-05-21 165648.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/b2af1b8b-bc5e-fc7e-e880-ad9295340401.png)
ちなみに、私がテナント管理者となっているテナント内で作成した`Microsoft Teams`のチャネル内におけるメッセージは以下の通りになります。これにより、`Microsoft Graph`で`Teams`メッセージを正しく取得できたことを確認することができました。
![スクリーンショット 2023-05-21 165833.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/bfba3c4b-c7b2-4af5-07eb-ada96c46d2b9.png)

# おわりに
本記事では`Microsoft Graph`を使って`Teams`メッセージを取得するために、`OAuth2.0`の認可フローに触れながら解説させていただきました。今回は`Microsoft Teams`を対象としていますが、`Outlook`や`OneDrive`といった製品についても、`Microsoft Graph`を使えばアクセスすることができるため、本記事の内容の理解を深めれば、応用の幅が広がると思います。私は実のところ、今回初めて`Microsoft Graph`に触れましたが、認証・認可について理解があれば簡単に使用できて便利なAPIであると感じたので、この記事を読んでくださった方は是非このAPIを使ってみてください。私もまだまだAzureについて勉強している途中なので、本記事の内容で間違っている箇所があればご教授していただけると嬉しいです。