読者です 読者をやめる 読者になる 読者になる

【C#】Google APIを使うためにOauth2認証する

GoogleのAPI Consoleにプロジェクトを登録すると、Googleの膨大な資産で遊べたり、Googleアカウントを利用して各サービスのAPIを叩いたりすることが出来ます。

更に各言語でGoogle APIを叩くためのライブラリが無料で公開されているため、自分の好きな言語で思っている以上に簡単に遊ぶことが出来ます。

このライブラリはGoogle Data APIsライブラリとはまったく違うものです。

Google Data APIsのページにも「The Google Data APIs documentation applies only to the older APIs」と書かれている通り、Google Data APIsは古いAPIであり今は使えないので、無駄にインストールしないようにしましょう。私はしてしまいました。

って言うか、Google側でリダイレクトしてくれよ、とも思うんですけどね。巷のGoogle APIに関する記事もGoogle Data APIsの頃に書かれたものが多いので、場合によっては全く参考にならなかったりします。きっとこの記事もそのうち全く役に立たなくなるでしょう。Androidの互換性もこれぐらい派手にぶっ壊してくれないかなぁ。

そんなわけで今回は.NET用のライブラリでInstalled applicationでのOauth2認証をやってみます。Googleと.NETがイメージとして結びつきにくいからでしょうか、ぜーんぜん記事がなくて中々面倒でした。わかると非常に簡単なんですけどね。

Web applicationとかService accountとかだとまた全然やり方が違うんでしょうが、私が興味ないのでやりません。頑張ってね。

API Consoleにプロジェクトを登録する

そもそもAPI Console自体がUIごと派手に更新されまくってるせいで「こうやればいいですよ〜」みたいな記事もあっという間に役に立たなくなります。

2014年01月23日時点での登録方法はこんな感じです。画像無しで説明するのが死ぬほど辛いUIなので、リンクを張ってお茶を濁しておきます。

とりあえず今回必要なのはClient IDとClient secretだけなので、頑張って取ってきてください。

Nugetで必要なものをインストールする

まぁ今時.NETでライブラリをインストールさせるのにNugetが用意されてなかったら「おいおい(笑)」みたいな気分になるんですが、ちゃんと用意されてます。流石ですね。

ここから自分の使いたいサービスを選択すると詳細なドキュメントへのリンクとNugetへのリンクが表示されます。

例えばGoogle Play Android Developer API Client Library for .NETとか言うわざわざ.NETでは叩かなそうなサービスを選択すると、「Downloading the library」と言うセクションがちょっと下にあり、nuget galleryへのリンクが紹介されています。

勿論Nuget経由ですので、Oauth認証に必要なライブラリとか、その他依存関係のあるライブラリは全部まとめてインストールされます。

Oauth2認証をやってみる

じゃあいきなりコードです。今回私はGoogle AnalyticsAPIをゴニョゴニョしたい気分なので、それをNugetでインストールしてあります。

using System;
using System.Threading;
using Google.Apis.Analytics.v3;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Util.Store;
using Google.Apis.Services;

namespace WindowsFormsApplication1
{
    class AnalyticsServiceWrapper : IDisposable
    {
        private const string CLIENT_ID = "";
        private const string CLIENT_SECRET = "";
        private const string API_KEY = "";
        private const string PROFILE_ID = "";

        private readonly AnalyticsService _service;

        public AnalyticsServiceWrapper()
        {
            var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets
            {
                ClientId = CLIENT_ID,
                ClientSecret = CLIENT_SECRET
            }, new[] { AnalyticsService.Scope.Analytics }
                , "user"
                , CancellationToken.None
                , new FileDataStore("TumblingDiceAnalytics")
            ).Result;

            _service = new AnalyticsService(new BaseClientService.Initializer
            {
                HttpClientInitializer = credential,
                ApplicationName = "TumblingDice",
                ApiKey = API_KEY,
            });
        }

        public void Dispose()
        {
            if (_service != null) _service.Dispose();
        }

        ~AnalyticsServiceWrapper()
        {
            if (_service != null) _service.Dispose();
        }
    }
}

コンストラクタとDisposeとデストラクタしかありません。今リアルにここまでしか書いてないからです。のんびり作ります。

まぁそれはどうでもよくて、Developer’s GuideのOAuth 2.0に関するドキュメントの例と突合せながら解説していきます。

ドキュメントではこんな例が表示されています。

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"));
}

見ての通りバリバリawaitを使ってます。クールですね。私の環境は4.0なので全部Resultでとってきています。ダサいです。

さて、いきなりFileStreamでjsonを読もうとしてます。なんぞこれ?と思うのは当然なんですが、これはClient Secretsのドキュメントに載っています。こう言ったjsonを作っておくことでコードに依存せずにWeb ApplicationやInstalled applicationを切り替えられるわけですね。

別にハードコーディングしちゃってもいいんだけど、って場合は私が書いたコードのようにしてしまっても問題ありません。

var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets
            {
                ClientId = CLIENT_ID,
                ClientSecret = CLIENT_SECRET
            }, new[] { AnalyticsService.Scope.Analytics }
                , "user"
                , CancellationToken.None
                , new FileDataStore("TumblingDiceAnalytics")
            ).Result;

次に謎の配列です。

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"));
}

これはScopeと呼ばれており、どうやらデータを操作できる範囲の許可を選べるみたいです。どのサービスであっても「○○(使いたいサービス)Service.Scope」と言うenumに全部登録されてるので、Intellisenseでそれっぽいやつを選んでください。

次に謎の「user」と言う文字列…なんですが、正直よくわかってません。パラメータ名は「userId」となっているんですが、例のままuserって文字列を渡しても何の問題もありませんし、またuser以外の文字列を渡している例や解説も見つかってません。何なんでしょうね?

CancellationTokenは直感でわかると思うので飛ばしてFileDataStoreについて説明します。

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"));
}
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets
            {
                ClientId = CLIENT_ID,
                ClientSecret = CLIENT_SECRET
            }, new[] { AnalyticsService.Scope.Analytics }
                , "user"
                , CancellationToken.None
                , new FileDataStore("TumblingDiceAnalytics")
            ).Result;

これは再度Oauth認証しなくても済むよう自動でキャッシュしてくれます。認証に成功すると<ユーザ名>AppData\Roamingにコンストラクタで渡した名前のフォルダを作り、その中にJSONがそのまま放り込まれます。オプション引数となっていますが、指定しない or nullを渡すとデフォルトのフォルダが作られます。

で、このAuthorizeAsyncを呼ぶと、自動でブラウザが立ち上がり認証画面が表示されます。

多分皆さんは全然関係ないと思いますが、内部でProcess.Start(url)されてるので、httpsを開けるよう規定のプログラムを設定していないと「ファイルの関連付けがないから開けない」と容赦なくWin32Exceptionが飛んできます。私はこれで半日潰しました。

もし万が一同じ事象に悩まされてしまった人はこれを参考にうまいこと規定のブラウザを設定してあげてください。

認証結果を使ってServiceを作成する

無事に認証が成功したら後は使いたいサービスのインスタンスを作成します。

ドキュメントの例ではこんな感じですね。

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

私が書いたコードはこんな感じです。API Keyがいるのかいらないのか、よくわかりません。

ApplicationNameは多分何でもいいと思います。

_service = new AnalyticsService(new BaseClientService.Initializer
{
    HttpClientInitializer = credential,
    ApplicationName = "TumblingDice",
    ApiKey = API_KEY,
});

まとめ

後は各サービスの詳細なドキュメントとにらめっこして思い思いのアプリケーションを作ってみてください。

JSONベースだからか、この後はやたらと引数に文字列を渡させようとしてくると思いますが、APIs Explorerと言う便利なツールがあるのでこれでデバッグしながら頑張りましょう。頑張ります。

参考