OAuth 2.0

このドキュメントでは、OAuth 2.0、OAuth 2.0 を使用するタイミング、クライアント ID を取得する方法、.NET 用 Google API クライアント ライブラリで OAuth 2.0 を使用する方法について説明します。

OAuth 2.0 プロトコル

OAuth 2.0 は、Google API で使用される承認プロトコルです。以下のリンクを参照して、プロトコルをよく理解しておく必要があります。

クライアント ID とシークレットを取得する

クライアント ID とシークレットは、Google API Console で取得できます。クライアント ID にはさまざまな種類があるため、アプリケーションに適したタイプを取得してください。

表示されている各コード スニペット(サービス アカウントを除く)で、クライアント シークレットをダウンロードして、プロジェクトに client_secrets.json として保存する必要があります。

認証情報

ユーザー認証情報

UserCredential は、アクセス トークンを使用して保護されたリソースにアクセスするためのスレッドセーフ ヘルパークラスです。アクセス トークンは通常 1 時間で期限切れになります。期限切れ後にそのトークンを使用しようとするとエラーが返されます。

UserCredentialAuthorizationCodeFlow は、トークンを自動的に「更新」(新しいアクセス トークンを取得)します。これは、有効期間の長い更新トークンを使用して行われます。このトークンは、認可コードフロー中に access_type=offline パラメータを使用すると、アクセス トークンと共に受け取ります。

ほとんどのアプリケーションでは、認証情報のアクセス トークンと更新トークンを永続ストレージに保存することをおすすめします。そうでない場合、アクセス トークンは受信から 1 時間後に期限切れになるため、エンドユーザーにブラウザで認可ページを 1 時間ごとに表示する必要があります。

アクセス トークンと更新トークンを保持するには、独自の IDataStore の実装を提供するか、ライブラリで提供されている次のいずれかの実装を使用します。

  • .NET の FileDataStore を使用すると、認証情報がファイルに永続的に保存されます。

ServiceAccountCredential

ServiceAccountCredentialUserCredential に似ていますが、目的が異なります。Google OAuth 2.0 は、ウェブ アプリケーションと Google Cloud Storage 間など、サーバー間のインタラクションをサポートしています。リクエスト元のアプリケーションは、API へのアクセスを取得するために自身の ID を証明する必要があります。エンドユーザーが関与する必要はありません。ServiceAccountCredential には、新しいアクセス トークンを取得するためのリクエストの署名に使用される秘密鍵が保存されます。

UserCredentialServiceAccountCredential はどちらも IConfigurableHttpClientInitializer を実装しているため、それぞれ次のように登録できます。

  • 失敗したレスポンス ハンドラ。HTTP 401 ステータス コードを受信すると、トークンを更新します。
  • インターセプタ。すべてのリクエストで Authorization ヘッダーをインターセプトします。

インストールしたアプリケーション

Books API を使用したサンプルコード:

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

using Google.Apis.Auth.OAuth2;
using Google.Apis.Books.v1;
using Google.Apis.Books.v1.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;

namespace Books.ListMyLibrary
{
    /// <summary>
    /// Sample which demonstrates how to use the Books API.
    /// https://developers.google.com/books/docs/v1/getting_started
    /// <summary>
    internal class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            Console.WriteLine("Books API Sample: List MyLibrary");
            Console.WriteLine("================================");
            try
            {
                new Program().Run().Wait();
            }
            catch (AggregateException ex)
            {
                foreach (var e in ex.InnerExceptions)
                {
                    Console.WriteLine("ERROR: " + e.Message);
                }
            }
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }

        private async Task Run()
        {
            UserCredential credential;
            using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    new[] { BooksService.Scope.Books },
                    "user", CancellationToken.None, new FileDataStore("Books.ListMyLibrary"));
            }

            // Create the service.
            var service = new BooksService(new BaseClientService.Initializer()
                {
                    HttpClientInitializer = credential,
                    ApplicationName = "Books API Sample",
                });

            var bookshelves = await service.Mylibrary.Bookshelves.List().ExecuteAsync();
            ...
        }
    }
}
  
  • このサンプルコードでは、GoogleWebAuthorizationBroker.AuthorizeAsync メソッドを呼び出して新しい UserCredential インスタンスを作成します。この静的メソッドは、次を取得します。

    • クライアント シークレット(またはクライアント シークレットへのストリーム)。
    • 必要なスコープ。
    • ユーザー ID。
    • オペレーションをキャンセルするためのキャンセル トークン。
    • データストア(省略可)。データストアが指定されていない場合、デフォルトはデフォルトの Google.Apis.Auth フォルダを持つ FileDataStore です。フォルダは Environment.SpecialFolder.ApplicationData に作成されます。
  • このメソッドによって返される UserCredential は、(イニシャライザを使用して)BooksServiceHttpClientInitializer として設定されます。前述のように、UserCredentialHTTP クライアント イニシャライザを実装します。

  • サンプルコードでは、クライアント シークレット情報がファイルから読み込まれていますが、次のようにすることもできます。

    credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
        new ClientSecrets
        {
            ClientId = "PUT_CLIENT_ID_HERE",
            ClientSecret = "PUT_CLIENT_SECRETS_HERE"
        },
        new[] { BooksService.Scope.Books },
        "user",
        CancellationToken.None,
        new FileDataStore("Books.ListMyLibrary"));
          

書籍のサンプルをご覧ください。

ウェブ アプリケーション(ASP.NET Core 3)

Google API は、 ウェブサーバー アプリケーション用の OAuth 2.0 をサポートしています。

ASP.NET Core 3 アプリケーションのほとんどの Google ベースの OAuth 2.0 シナリオでは、 Google.Apis.Auth.AspNetCore3 ライブラリを使用することをおすすめします。Google 固有の OpenIdConnect 認証ハンドラを実装します。増分認証をサポートし、Google API で使用できる Google 認証情報を提供するインジェクタブルな IGoogleAuthProvider を定義します。

このセクションでは、Google.Apis.Auth.AspNetCore3 を構成して使用する方法について説明します。ここで示すコードは、完全に機能する標準の ASP.NET Core 3 アプリケーションである Google.Apis.Auth.AspNetCore3.IntegrationTests に基づいています。

このドキュメントをチュートリアルとして使用する場合は、独自の ASP.NET Core 3 アプリケーションが必要になります。また、前提条件として、次の手順を完了する必要があります。

前提条件

  • Google.Apis.Auth.AspNetCore3 パッケージをインストールします。
  • Google Drive API を使用しているため、Google.Apis.Drive.v3 パッケージもインストールする必要があります。
  • Google Cloud プロジェクトをまだ作成していない場合は作成します。 こちらの手順に沿って作成します。このプロジェクトがアプリの識別子になります。
  • Google Drive API が有効になっていることを確認します。API を有効にするには、 こちらの手順を行ってください。
  • Google に対してアプリを識別する承認認証情報を作成します。 こちらの手順に沿って認証情報を作成し、client_secrets.json ファイルをダウンロードします。2 つのハイライト:
    • 認証情報のタイプは [ウェブ アプリケーション] にする必要があります。
    • このアプリを実行するために追加する必要があるリダイレクト URI は https://localhost:5001/signin-oidc のみです。

Google.Apis.Auth.AspNetCore3 を使用するようにアプリケーションを構成する

Google.Apis.Auth.AspNetCore3 は、Startup クラスまたは使用している類似のクラスで構成されます。次のスニペットは、Google.Apis.Auth.AspNetCore3.IntegrationTests プロジェクトの Startup.cs から抽出されています。

  • Startup.cs ファイルに次の using ディレクティブを追加します。
    using Google.Apis.Auth.AspNetCore3;
  • Startup.ConfigureServices メソッドに次のコードを追加し、クライアント ID とクライアント シークレットのプレースホルダを client_secrets.json ファイルに含まれる値に変更します。これらの値は JSON ファイルから直接読み込むことも、他の安全な方法で保存することもできます。これらの値を JSON ファイルから直接読み込む方法の例については、Google.Apis.Auth.AspNetCore3.IntegrationTests プロジェクトの ClientInfo.Load メソッドをご覧ください。
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        // This configures Google.Apis.Auth.AspNetCore3 for use in this app.
        services
            .AddAuthentication(o =>
            {
                // This forces challenge results to be handled by Google OpenID Handler, so there's no
                // need to add an AccountController that emits challenges for Login.
                o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
                // This forces forbid results to be handled by Google OpenID Handler, which checks if
                // extra scopes are required and does automatic incremental auth.
                o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
                // Default scheme that will handle everything else.
                // Once a user is authenticated, the OAuth2 token info is stored in cookies.
                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddCookie()
            .AddGoogleOpenIdConnect(options =>
            {
                options.ClientId = {YOUR_CLIENT_ID};
                options.ClientSecret = {YOUR_CLIENT_SECRET};
            });
    }
          
  • Startup.Configure メソッドで、ASP.NET Core 3 認証と承認のミドルウェア コンポーネントと HTTPS リダイレクトをパイプラインに追加します。
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...
        app.UseHttpsRedirection();
        ...
    
        app.UseAuthentication();
        app.UseAuthorization();
    
        ...
    }
          

ユーザーの認証情報を使用してユーザーに代わって Google API にアクセスする

これで、ユーザーの代わりに Google API にアクセスするためにユーザー認証情報を必要とするアクション メソッドをコントローラに追加する準備が整いました。次のスニペットは、認証済みユーザーの Google ドライブ アカウントのファイルを一覧表示する方法を示しています。主に次の 2 つの点に注意してください。

  • ユーザーは認証されるだけでなく、https://www.googleapis.com/auth/drive.readonly スコープをアプリケーションに付与する必要があります。これは GoogleScopedAuthorize 属性を使用して指定します。
  • Google では、ASP.NET Core 3 の標準依存関係注入メカニズムを使用して IGoogleAuthProvider を受け取り、ユーザーの認証情報を取得しています。

コード:

  • まず、コントローラに次の using ディレクティブを追加します。
    using Google.Apis.Auth.AspNetCore3;
    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Drive.v3;
    using Google.Apis.Services;
          
  • 次のようにコントローラ アクションを追加します(IList<string> モデルを受信するビューを追加します)。
    /// <summary>
    /// Lists the authenticated user's Google Drive files.
    /// Specifying the <see cref="GoogleScopedAuthorizeAttribute"> will guarantee that the code
    /// executes only if the user is authenticated and has granted the scope specified in the attribute
    /// to this application.
    /// </summary>
    /// <param name="auth">The Google authorization provider.
    /// This can also be injected on the controller constructor.</param>
    [GoogleScopedAuthorize(DriveService.ScopeConstants.DriveReadonly)]
    public async Task<IActionResult> DriveFileList([FromServices] IGoogleAuthProvider auth)
    {
        GoogleCredential cred = await auth.GetCredentialAsync();
        var service = new DriveService(new BaseClientService.Initializer
        {
            HttpClientInitializer = cred
        });
        var files = await service.Files.List().ExecuteAsync();
        var fileNames = files.Files.Select(x => x.Name).ToList();
        return View(fileNames);
    }
          

以上が基本的な内容です。Google.Apis.Auth.AspNetCore3.IntegrationTests プロジェクトの HomeController.cs で、次の方法を確認できます。

  • ユーザー認証のみ、特定のスコープなし
  • ユーザーのログアウト
  • コードを使用した段階的な認可。このサンプルでは、属性を使用した増分承認を示しています。
  • 付与されたスコープを確認する
  • アクセス トークンと更新トークンを調べる
  • アクセス トークンを強制的に更新します。Google.Apis.Auth.AspNetCore3 は、アクセス トークンが期限切れか期限切れ間近かどうかを検出し、自動的に更新するため、この操作は必要ありません。

サービス アカウント

Google API はサービス アカウントもサポートしています。クライアント アプリケーションがエンドユーザーのデータへのアクセスをリクエストするシナリオとは異なり、サービス アカウントはクライアント アプリケーション独自のデータへのアクセスを提供します。

クライアント アプリケーションは、Google API Console からダウンロードした秘密鍵を使用して、アクセス トークン リクエストに署名します。新しいクライアント ID を作成したら、サービス アカウント アプリケーションの種類を選択し、秘密鍵をダウンロードします。 Google Plus API を使用するサービス アカウントのサンプルをご覧ください。

using System;
using System.Security.Cryptography.X509Certificates;

using Google.Apis.Auth.OAuth2;
using Google.Apis.Plus.v1;
using Google.Apis.Plus.v1.Data;
using Google.Apis.Services;

namespace Google.Apis.Samples.PlusServiceAccount
{
    /// <summary>
    /// This sample demonstrates the simplest use case for a Service Account service.
    /// The certificate needs to be downloaded from the Google API Console
    /// <see cref="https://console.cloud.google.com/">
    ///   "Create another client ID..." -> "Service Account" -> Download the certificate,
    ///   rename it as "key.p12" and add it to the project. Don't forget to change the Build action
    ///   to "Content" and the Copy to Output Directory to "Copy if newer".
    /// </summary>
    public class Program
    {
        // A known public activity.
        private static String ACTIVITY_ID = "z12gtjhq3qn2xxl2o224exwiqruvtda0i";

        public static void Main(string[] args)
        {
            Console.WriteLine("Plus API - Service Account");
            Console.WriteLine("==========================");

            String serviceAccountEmail = "SERVICE_ACCOUNT_EMAIL_HERE";

            var certificate = new X509Certificate2(@"key.p12", "notasecret", X509KeyStorageFlags.Exportable);

            ServiceAccountCredential credential = new ServiceAccountCredential(
               new ServiceAccountCredential.Initializer(serviceAccountEmail)
               {
                   Scopes = new[] { PlusService.Scope.PlusMe }
               }.FromCertificate(certificate));

            // Create the service.
            var service = new PlusService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "Plus API Sample",
            });

            Activity activity = service.Activities.Get(ACTIVITY_ID).Execute();
            Console.WriteLine("  Activity: " + activity.Object.Content);
            Console.WriteLine("  Video: " + activity.Object.Attachments[0].Url);

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }
}

このサンプルでは、ServiceAccountCredential を作成します。必要なスコープが設定され、FromCertificate が呼び出されます。これにより、指定された X509Certificate2 から秘密鍵が読み込まれます。他のすべてのサンプルコードと同様に、認証情報は HttpClientInitializer として設定されています。