OAuth2認証で、Gmailからメール情報を受信する[C#]
Gmailのデータを取得するための従来の方法として、「POP3」や「IMAP」がありました。しかし、POP3は2022年5月に、IMAPは2024年6月にそれぞれ利用できなくなりました。そのため、今後はOAuth2認証に移行する必要があります。ここでは、OAuth2認証の方法についてメモを示します。
1.OAuth2の意義
POPやIMAPでは、ユーザーのIDとパスワードを入力することで、Gmail以外のサービスからもメールにアクセスすることが可能でした。
しかし、この方法にはいくつかの問題点がありました。
まず、IDとパスワードを外部のサービスに提供することで、セキュリティリスクが増大します。これらの認証情報が漏洩した場合、不正アクセスやアカウントの乗っ取りなどの被害が発生する可能性があります。
また、ユーザーが複数のサービスで同じパスワードを使い回している場合、一箇所で情報が漏洩すると他のサービスにも影響を及ぼす恐れがあります。
このような問題を解決するために、GoogleはOAuth2への移行を推奨しています。OAuth2は、IDやパスワードを直接共有することなく、安全にリソースへのアクセスを提供する認証プロトコルです。具体的には、次のような利点があります。
トークンベースの認証:OAuth2では、ユーザーのIDとパスワードではなく、アクセストークンを使用して認証を行います。これにより、認証情報の漏洩リスクが低減されます。
限定的なアクセス権:アクセストークンには、有効期限やアクセス範囲(スコープ)を設定することができます。これにより、必要最小限の権限だけを外部サービスに付与することができ、セキュリティを強化します。
トークンの無効化:トークンが漏洩した場合でも、管理者がトークンを無効化することが可能です。これにより、迅速にセキュリティ対策を講じることができます。
ユーザーの管理:OAuth2は、ユーザーがいつ、どのサービスにアクセスを許可したかを管理できるため、不必要なアクセス権を簡単に取り消すことができます。
以上の理由から、GoogleはOAuth2への移行を進めており、これによりユーザーのデータをより安全に保護することが可能となるのです。
2.クライアント側の準備
2-1.Google CloudでのOAuth2.0 クライアント側登録
1.Google Cloudコンソールにアクセス
Google Cloudコンソールにログインし、対象のプロジェクトを選択します。プロジェクトがまだ作成されていない場合は、新しいプロジェクトを作成してください。
2.APIとサービスを選択
コンソールのメインダッシュボードから「APIとサービス」セクションに移動します(画像の赤丸で示されています)。
3.ライブラリを開く
「APIとサービス」メニューの左側のナビゲーションから「ライブラリ」を選択します。ここでは、使用可能なAPIを検索して有効化することができます。
4.Gmail APIを有効にする
ライブラリから「Gmail API」を検索し、選択します。次に、表示される画面で「有効にする」ボタンをクリックしてGmail APIを有効にします。
5.認証情報の作成
認証情報の作成をクリックし、認証情報登録画面を開きます。
6.OAuth同意画面の設定
アプリケーション名、サポートメール、開発者の連絡先情報など、必要な情報を入力します。これにより、ユーザーがアプリケーションを認識し、同意するための情報が設定されます。必要な情報を入力したら「保存して次へ」をクリックします。
7.スコープの設定
アプリケーションがアクセスを要求するスコープを追加します。スコープとは、アプリがユーザーのどのデータにアクセスできるかを定義するものです。必要なスコープを選択し、「保存して次へ」をクリックします。
8.OAuthクライアントIDの設定
「OAuthクライアントID」セクションで、アプリケーションの種類を選択します。ここでは「デスクトップアプリ」を選択し、名前を入力します。入力が完了したら「作成」をクリックします。
9.認証情報のダウンロード
認証情報が生成されると、クライアントIDが表示されます。この情報をJSON形式でダウンロードし、安全に保管します。これらの認証情報は後ほどアプリケーションで使用します。
2-2.テストユーザーの追加
クライアント側のGoogleアカウントとテストGoogleアカウントが異なる場合は、テストユーザーを追加する必要があります。
「OAuth同意画面」の左側メニューから「テストユーザー」セクションに移動し、「+ ADD USERS」ボタンをクリックしてテストユーザーを追加します。
3.プログラムの準備
3-1.パッケージのインストール
C#にて、OAuth 2.0認証でGmailの内容を取得するには、「Google.Apis.Auth」「Google.Apis.Gmail」が必要となるため、インストールしておきます。
3-2.UIの実装
UIをテキトーに実装します。
namespace popNet_test
{
partial class Form1
{
/// <summary>
/// 必要なデザイナー変数です。
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 使用中のリソースをすべてクリーンアップします。
/// </summary>
/// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows フォーム デザイナーで生成されたコード
/// <summary>
/// デザイナー サポートに必要なメソッドです。このメソッドの内容を
/// コード エディターで変更しないでください。
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.labelHostname = new System.Windows.Forms.Label();
this.listView1 = new System.Windows.Forms.ListView();
this.label4 = new System.Windows.Forms.Label();
this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
this.textBox1 = new System.Windows.Forms.TextBox();
this.Btn_Path = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(617, 732);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(188, 43);
this.button1.TabIndex = 0;
this.button1.Text = "受信";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// labelHostname
//
this.labelHostname.AutoSize = true;
this.labelHostname.Location = new System.Drawing.Point(12, 46);
this.labelHostname.Name = "labelHostname";
this.labelHostname.Size = new System.Drawing.Size(43, 18);
this.labelHostname.TabIndex = 1;
this.labelHostname.Text = "Path";
//
// listView1
//
this.listView1.HideSelection = false;
this.listView1.Location = new System.Drawing.Point(73, 90);
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(694, 618);
this.listView1.TabIndex = 12;
this.listView1.UseCompatibleStateImageBehavior = false;
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(12, 90);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(55, 18);
this.label4.TabIndex = 13;
this.label4.Text = "Result";
//
// openFileDialog1
//
this.openFileDialog1.FileName = "openFileDialog1";
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(73, 43);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(613, 25);
this.textBox1.TabIndex = 14;
//
// Btn_Path
//
this.Btn_Path.Location = new System.Drawing.Point(692, 38);
this.Btn_Path.Name = "Btn_Path";
this.Btn_Path.Size = new System.Drawing.Size(75, 35);
this.Btn_Path.TabIndex = 15;
this.Btn_Path.Text = "参照";
this.Btn_Path.UseVisualStyleBackColor = true;
this.Btn_Path.Click += new System.EventHandler(this.Btn_Path_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 18F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(817, 787);
this.Controls.Add(this.Btn_Path);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.label4);
this.Controls.Add(this.listView1);
this.Controls.Add(this.labelHostname);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label labelHostname;
private System.Windows.Forms.ListView listView1;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.OpenFileDialog openFileDialog1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button Btn_Path;
}
}
3-3.コードの実装
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
using Google.Apis.Gmail.v1.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using System.IO;
using System.Threading;
namespace popNet_test
{
public partial class Form1 : Form
{
private const string ApplicationName = "Stock BOX";
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// ListViewの設定
listView1.View = View.Details;
listView1.Columns.Add("Subject", 400);
}
private async void button1_Click(object sender, EventArgs e)
{
try
{ // スコープ設定で指定したURLを追加
string[] Scopes = { GmailService.Scope.GmailReadonly };
UserCredential credential;
// Token の取得
using (var stream = new FileStream(textBox1.Text, FileMode.Open, FileAccess.Read))
{
string credPath = "token.json";
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.FromStream(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true));
}
await FetchEmails(credential);
}
catch (Exception ex)
{
MessageBox.Show($"An error occurred: {ex.Message}");
}
}
private async Task FetchEmails(UserCredential credential)
{
try
{
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
// ユーザーのメールリストを取得
var request = service.Users.Messages.List("me");
request.LabelIds = "INBOX";
request.MaxResults = 10;
var response = await request.ExecuteAsync();
listView1.Items.Clear();
if (response.Messages != null && response.Messages.Count > 0)
{
foreach (var msg in response.Messages)
{
var messageRequest = service.Users.Messages.Get("me", msg.Id);
var message = await messageRequest.ExecuteAsync();
string subject = message.Payload.Headers.FirstOrDefault(h => h.Name == "Subject")?.Value;
// リストビューに表示
ListViewItem item = new ListViewItem(subject);
listView1.Items.Add(item);
}
}
else
{
MessageBox.Show("No messages found.");
}
}
catch (Exception ex)
{
MessageBox.Show($"An error occurred: {ex.Message}");
}
}
private void Btn_Path_Click(object sender, EventArgs e)
{
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*";
openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
// 選択したファイルパスをテキストボックスに表示
textBox1.Text = openFileDialog.FileName;
}
}
}
}
}
4.コードの実行
参照ボタンを押して、ダウンロードしたJsonファイルをロードし、受信ボタンを押すと、認証したアカウントのメールタイトルが表示されます。
5.最後に
OAuth2を利用することで、ユーザーから直接パスワードやIDをもらう必要がなくなり、セキュリティが大幅に向上します。これにより、ユーザーの認証情報が第三者に漏洩するリスクを低減できます。
また、一度取得したアクセストークンを保存しておくことで、後続のアクセス時に再認証が不要になるため、ユーザーエクスペリエンスが向上します。