だるろぐ

明日できることは、今日しない。

UWP:はてなの oAuth 認証

f:id:daruyanagi:20160919220838p:plain

プログラミングの秋……なのかな? 最近、またプログラミングをちょろちょろとやっています。今回は、UWP。一つ新しいのができたのでストアに提出して、今はむかし作りかけて放置中のはてブをつけるアプリを完成させようと四苦八苦してる途中。

実はこのアプリ、ほぼ完成していて、Windows 10 Mobile ではてブみたりはてブつけるのに使っているのだけど、セットアップに AtomPub の APIキー を使うタイプなんだよね。自分で使う分にはこれでもいいんだけど、ちょっとダサいので oAuth でやりたかった。

AsyncOAuth を導入する

さて、oAuth はトークンのやり取りとかハッシュとかヘッダーの生成とか、いろいろややこしい処理がある。自分でも組んでみたけど、どこかちょっと間違ってるみたいで、なかなか認証が通らない。というわけで、ライブラリさまのお力を借りた。

いろいろ探してみたんだけど、AsyncOAuth が一番気に入ったかも。

UWP プロジェクトに NuGet でインストールできなかったんだけど、手動で加えたら問題なく動いた。もうメンテナンスされていないのかもしれないけど、せっかくいいものなので UWP でもサクッと使えるようにしてほしいな(賛同してくれるヒトは、みんなで のいえっち にサイレントプレッシャーをかけよう!)。

AsyncOAuth にはコンソールアプリだけど はてな 認証のサンプルもついてる。これを UWP 向けにチョロチョロと改造すればおっけ。

var authorizer = new OAuthAuthorizer(ConsumerKey, ConsumerSecret);
var callbackUri = "http://localhost/";

var requestTokenResponse = await authorizer.GetRequestToken(
    "https://www.hatena.com/oauth/initiate",
    new[]
    { 
        new KeyValuePair<string, string>(
            "oauth_callback",
            callbackUri
        )
    },
    new FormUrlEncodedContent(new[] {
        new KeyValuePair<string, string>(
            "scope", 
            "read_public,write_public,read_private,write_private"
        )
    }));

var requestToken = requestTokenResponse.Token;

var authorizeUrl = authorizer.BuildAuthorizeUrl(
    "https://www.hatena.ne.jp/touch/oauth/authorize", 
    requestToken);

たとえば、リクエストトークンをとるまでのところはこんな感じ。めんどくさいところは全部やってくれるので楽ちんだー。

callbackUri を“oob”にしたら PIN を使った認証になる。WebAuthenticationBroker.GetCurrentApplicationCallbackUri() で取得した URL は はてな では使えないみたいなので、今回は“http://localhost/”をセットしておく(これはあとで認証するときに必要になる)。

WebAuthenticationBroker を使って認証する

f:id:daruyanagi:20160919223009p:plain

次は Web へアクセスして認証。UWP には WebAuthenticationBroker っていうダイアログを出す仕組み(それだけじゃないけど)が用意されているので、それを使うといい感じ。

// さっき取得したリクエストトークンを使って認証 URI を取得
var authorizeUrl = authorizer.BuildAuthorizeUrl(
    "https://www.hatena.ne.jp/touch/oauth/authorize",
     requestToken);

// ダイアログを開いて結果を取得
var webAuthenticationResult = await WebAuthenticationBroker
    .AuthenticateAsync(
        WebAuthenticationOptions.None,
        new Uri(authorizeUrl), new Uri(callbackUri)
    );

// 結果をみていろいろ分岐処理
switch (webAuthenticationResult.ResponseStatus)
{
    case WebAuthenticationStatus.Success:
        try
        {
            // http://localhost?...=...&...=... っていうのが
            // 返るのでテキトーに千切って verifier だけ取得
            var verifier = webAuthenticationResult.ResponseData
                .Split('?').Last()
                .Split('&').Last()
                .Split('=').Last();
            // リクエストトークンと verifier で
            // アクセストークンをゲット
            // これでいろいろし放題だ!
            var accessTokenTokenResponse = await authorizer
                .GetAccessToken(
                    "https://www.hatena.com/oauth/token",
                    requestToken, verifier);

            AccessToken = accessTokenTokenResponse.Token;

            result = true;
        }
        catch (Exception exception)
        {
            // エラー処理するんやで
        }
        break;

    case WebAuthenticationStatus.ErrorHttp:
    case WebAuthenticationStatus.UserCancel:
    default:
        break;
}

ユーザー情報を取得

f:id:daruyanagi:20160919223808p:plain

あとは OAuthUtility.CreateOAuthClient() でクライアントを取得してごにょごにょするだけ。このクライアントは AsyncOAuth が HttpClient にフィルター(?)をかましているだけなので、HttpClient みたいに GetAsync() とかできる。

var client = OAuthUtility.CreateOAuthClient(
    ConsumerKey, ConsumerSecret, AccessToken
);

var json = await client.GetStringAsync(
    "http://n.hatena.com/applications/my.json"
);

これでディスプレイネームやプロフィールイメージなんかが取得できるので、テキトーにバインディングしてあげればおっけ。楽ちんでいいわー。