OAuth認証によるgmailでのメール送受信についてのメモ

## このメモの目的
SQLServerからgmailに送受信できるようなライブラリを作ろうとして、gmailのOAuth2を調べていた時にWeb上の記事などで分かりにくかった点があったので、それをメモとして残すことにしました。

## GoogleのOAuthについて
OAuthについての説明は、次の記事が分かりやすいと思います。

https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be


この記事の用語を用いてGoogleのgmailへのOAuthによるアクセスの流れを説明すると、次のようになると思います。

１）プログラム作成者は、Google API ConsoleでOAuthクライアントIDを作成する

２）クライアントアプリは、認可サーバにclient_idおよびclient_secretを送るとブラウザでの許可が求められ、ユーザが許可すると認証コードが取得できる

３）クライアントアプリは、認証コードをもとに認可サーバからaccess_tokenを取得する。このとき、タイミングによってrefresh_tokenも取得できる

４）クライアントアプリは、（有効期限内の）access_tokenを使って、smtpサーバやimapサーバ、popサーバにアクセスする

５）クライアントアプリは、access_tokenの期限が切れた場合は、refresh_tokenを使ってaccess_tokenの更新を行う


最初のOAuthクライアントIDの取得の詳細については、こちらのサイトなどを参考にしました。

https://sqripts.com/2022/08/25/20386/

https://www.y-hakopro.com/entry/google_oauth_api

https://qiita.com/ksuzuki_TVDn1/items/9b2f8d1f24ed81456793

OAuthクライアントIDの取得の流れは次のようになります。

1-1） 新規プロジェクトの作成

1-2） GmailのAPIの有効化

1-3） OAuth同意画面の作成
  ・アプリケーションの種類：　デスクトップアプリ
  ・UserType：　外部
  ・スコープ：　https://mail.google.com/

1-4） 認証情報を作成（認証情報JSONをダウンロード）


以上の流れを整理するとOAuthの手順はつぎのとおりとなります。

手順：
１）プログラム作成者は、Google API ConsoleでOAuthクライアントIDを作成する

１-1） 新規プロジェクトの作成

１-2） GmailのAPIの有効化

１-3） OAuth同意画面の作成
  ・アプリケーションの種類：　デスクトップアプリ
  ・UserType：　外部
  ・スコープ：　https：//mail.google.com/

１-4） 認証情報を作成（JSONをダウンロード）

２） クライアントアプリは、認可サーバにclient_idおよびclient_secretを送るとブラウザでの許可が求められ、ユーザが許可することで認証コードを取得する

３） クライアントアプリは、認証コードをもとに認可サーバからaccess_tokenを取得する。このとき、タイミングによってrefresh_tokenも取得できる

４） クライアントアプリは、（有効期限内の）access_tokenを使って、smtpサーバやimapサーバ、popサーバにアクセスする

５） クライアントアプリは、access_tokenの期限が切れた場合は、refresh_tokenを使ってaccess_tokenの更新を行う


# 気を付けるべき点

前述のGoogleのOAuthの流れのなかで、気を付けるべき点を挙げていきます。

### （１）本当にOAuthクライアントIDが必要ですか？

OAuthクライアントIDを使って認証するのは、「自分でメールクライアントアプリを作って配布する」ようなケースを想定したものです。
個人が自分のアカウントだけを対象に認証するなら、APIキーの発行で済む（らしい）のです。
ですが、そのように説明してくれるサイトがあまりないです。

OAuthクライアントIDの発行で、手順１-3）同意画面の作成の段階でドメインや連絡用のメールアドレスの登録が必要ですし、本番環境に公開する際にはデスクトップアプリケーションの場合は次の項目を登録する必要があります。

<dl>
    <dt>１．アプリのプライバシーポリシーへのリンク</dt>
    <dt>２．スコープから取得したデータをどのように使用する予定であるかを示すYoutube動画</dt>
    <dt>３．機密データや制限付きのユーザデータへのアクセス権が必要である理由</dt>
</dl>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369494/3e64fef4-13af-0b6c-7197-91be91a62bfc.png)

なかなか、個人が使うだけには面倒なことではないかと感じます。
また、本番環境に公開せず、テスト環境のままなら、ブラウザで次の画面が出てきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369494/2f02d06a-bfb6-76ef-6f76-d4fc3f7bb895.png)


### （２）スコープに「https：//mail.google.com/」が必要ですか？

同様に、順１-3）同意画面の作成の段階で指定するスコープに「https：//mail.google.com/」の意味は、すべてのアカウントのメールへのアクセスを表しています。
なので自分のアカウントだけならここまでのスコープは必要なく、「https：//www.googleapis.com/auth/gmail.modify」などで行えるはずです。
これも、「自分でメールクライアントアプリを作って配布する」ようなケースを想定したものです。
ですが、情報を調べるとスコープに「https：//mail.google.com/」を使用する例が多いと感じますし、それが必要だと説明されている方は少ないように感じます。
OAuthクライアントIDを発行しようとしている方、本当に必要か確認してみてください。


### （３）コード発行は複数回可能だが、access_token発行は一回のみ

自分で試しただけなので確実ではないですが、手順２）の認証コード取得は何回でも実行ですが、手順３）のaccess_tokenの取得（認証コードを渡してaccess_tokenを取得）は一回のみで二回目にはエラーが返されました。
セキュリティ的に考えれば当たり前なんですが、それを踏まえてaccess_tokenの有効期限が切れていて、refresh_tokenも無効になった場合を考えると、認証コードからaccess_tokenを発行することはできないことになり、client_idとclient_secretから認証コードの発行を行う必要があります。
つまり、認証コードそのものを保存する意味はなく、client_idとclient_secretを保存するか、もしくはaccess_tokenとrefresh_tokenを更新しながら保持していくことになります。
ライブラリとしてプログラムを作成する際に注意が必要です。


### （４）認証コード取得時のスコープの指定に注意

手順２）では、認証コードのリクエストにスコープを含める必要があり、リザルトとして認証コードとスコープが返されます。

リクエストでの引数の項目：　
- redirect_uri
- client_id
- state
- codeChallenge
- codeChallengeMethod
- scope

リザルトでの項目：
- code
- scope

リクエスト側のスコープは「こういう権限はありますか？」の意味で、リザルト側のスコープは「この認証コードはこういう権限に対応しています」の意味になります。

なので、手順１－3）でOAuth同意画面で含めていないスコープをリクエスト側で指定してもエラーになります。
反対にリクエスト側のスコープを指定しない（scope=''）なら、許可できる最大の権限が与えられることになります。

また、与えられる権限は、ブラウザに表示される内容に影響を与え「アクセスできる情報を選択してください」として選択するようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369494/49164ce8-fda1-ee86-49bb-7792c0339a75.png)

上図では、OAuth同意画面でスコープとして「openid」と「https：//mail.google.com/」を設定している状態で、リクエストのスコープを「openid%20https%3A//mail.google.com/」とした場合です。
「Gmailのすべてのメールの閲覧、作成、送信、完全な削除です。」の行にチェックを入れるとSMTPやIMAPで認証されるようになります。


### （５）サンプルコードは最新のものを使いましょう。

GoogleのOAuthのサンプルプログラムとしては、以下のサイトが紹介されることが多いようです。

https://github.com/google/gmail-oauth2-tools


しかし、次のサイトの方が新しいサンプルで参考になりました。

https://github.com/googlesamples/oauth-apps-for-windows

上のサイトが2012年、下のサイトが2016年のコードなのでより新しいものを使う方がいいですね。


### （６）認証コード取得とaccess_token取得時の引数を一致させること

手順２）の引数は次のものになります。
- redirect_uri *1
- client_id *1
- state *2
- code_challenge *2
- code_challenge_method *2
- scope

戻り値は次のものです。
- code *1
- scope *1

手順３）の引数は次のものになります。
- code *1
- redirect_uri *1
- client_id *1
- code_verifier *2
- client_secret *2
- scope *1

*1は一致する項目、*2は一緒の計算で算出した値を用いる。

これらの引数、戻り値は一致する必要があります。
state、code_challenge、code_challenge_method、codeVerifierは一緒に計算された値を用いる必要がありますし、redirect_uriとしてlocalhost:58001のように指定した場合はそのまま引数に指定する必要があるようです。
scopeは手順２）の戻り値と手順３）の引数が一致する形になります。

### （７）APIのエンドポイントは正しく指定する

認証コードを取得する場合のAPIエンドポイントは次のものです。

https：//accounts.google.com/o/oauth2/v2/auth


認証コードからaccess_token（とrefresh_token）を取得するAPIエンドポイントは次のものです。

https：//www.googleapis.com/oauth2/v4/token

refresh_tokenを使ってaccess_tokenを更新するAPIエンドポイントは次のものです。

https：//oauth2.googleapis.com/token

手入力でプログラムする場合などに間違えたり、古いURIが記載されていたりすることがあるので注意しましょう。


# おわりに

はじめに書いた通り、SQLServerからGmailに送受信できるようなライブラリを作りたいなと思ってるんですが、nugetのライブラリを使うとCLRとしてSQLServerに読み込ませるのに失敗したりして、手間がかかってます。
完成したら、記事にしてダイレクトマーケティングをしたいと思っていますので、その時はよろしくお願いします。