WebMatrix 3: Twitter でログインしてアクセストークン(秘)を取得する

執筆日時:

WebMatrix 3: Twitter でログインする - だるろぐ でめでたく Twitter でのログインが実現できたのだけど、実はひとつ問題があった。

f:id:daruyanagi:20130905064941p:plain

AccessTokenSecret が取れない。

自分もあんまりよくわかっていないのだけど、Twitter の API を利用するには以下の情報が必要であるみたい。

f:id:daruyanagi:20130911233354p:plain

まず、これ。アプリが Twitter へアクセスするために必要。

次に、これ。ユーザーに成り代わって Twitter の API を使うために必要。

アプリの登録画面で取得できる AccessKey/AccessKeySecret はアプリを登録したユーザーのアクセスキー。このアプリにログインしたユーザーとして API を利用するには、そのユーザーに対して発行される AccessKey/AccessKeySecret が必要だ。

でも、OAuthWebSecurity では ExtraData から AccessKey をもらうことはできても、AccessKeySecret まではくれないみたい。

f:id:daruyanagi:20130912000225p:plain

通信を Fiddler でみてみた。ちゃんと authorize したあとに access_token している(ここで AccessKey がもらえる)から、ついでに AccessKeySecret もとってきてくれてもよさそうなのだけど。なにか理由があるのかもしれないが、これではちょっと困る。

これを解決するには、Twitter プロバイダーを自分で実装すればよいようだ。

// ~/App_Code/TwitterClient.cs

using DotNetOpenAuth.AspNet; using DotNetOpenAuth.AspNet.Clients; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; using System.Collections.Generic;

// http://stackoverflow.com/questions/12198734/getting-twitter-access-secret-using-dotnetopenauth-in-mvc4

public class TwitterClient : OAuthClient { /// <summary> /// The description of Twitter’s OAuth protocol URIs for use with their "Sign in with Twitter" feature. /// </summary> public static readonly ServiceProviderDescription TwitterServiceDescription = new ServiceProviderDescription { RequestTokenEndpoint = new MessageReceivingEndpoint( "https://api.twitter.com/oauth/request_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), UserAuthorizationEndpoint = new MessageReceivingEndpoint( "https://api.twitter.com/oauth/authenticate", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), AccessTokenEndpoint = new MessageReceivingEndpoint( "https://api.twitter.com/oauth/access_token", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest), TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() }, };

public TwitterClient(string consumerKey, string consumerSecret) : base("twitter", TwitterServiceDescription, consumerKey, consumerSecret) { } protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response) { string accessToken = response.AccessToken; string accessSecret = (response as ITokenSecretContainingMessage).TokenSecret; string userId = response.ExtraData["user_id"]; string userName = response.ExtraData["screen_name"];

var extraData = new Dictionary<string, string>() { {"accesstoken", accessToken}, {"accesssecret", accessSecret} }; return new AuthenticationResult( isSuccessful: true, provider: ProviderName, providerUserId: userId, userName: userName, extraData: extraData); } }

結果はこんな感じ。いつもどおり ObjectInfo.Print() で中身を見てみたよ。

f:id:daruyanagi:20130912115257p:plain

実装としてどうするのが理想的なのかはよくわからないけれど、とりあえずユーザーを管理するテーブルを拡張して、アクセスキーを保管しておくのとかどうでしょうか。

@{
var returnUrl = Request["returnUrl"];

// ログインの検証
var result = OAuthWebSecurity.VerifyAuthentication(
Href("LogonCallBack", new { ReturnUrl = returnUrl })
);

if (result.IsSuccessful)
{
// ログインが成功すると、
// - provider: twitter
// - ProviderUserId: twitter の ID
// - UserName: twitter のスクリーンネーム
// の3つが得られる。自動補完が効かないので変数に入れとく
var provider = result.Provider;
var providerUserId = result.ProviderUserId;
var userName = result.UserName;
var accessToken = result.ExtraData["accesstoken"];
var accessTokenSecret = result.ExtraData["accesssecret"];

<p>@ObjectInfo.Print(result.ExtraData)</p>

// ユーザー名が Users テーブルに存在しない場合、
// あらかじめユーザー名を追加しておく。
// でないと CreateOrUpdateAccount() でコケる
using (var db = Database.Open("kenzou-memo"))
{
const string SELECT = "SELECT * FROM USERS WHERE Name=@0";
const string INSERT = "INSERT INTO Users (Name, AccessToken, AccessTokenSecret) VALUES (@0, @1, @2)";
const string UPDATE = "UPDATE Users SET AccessToken=@1, AccessTokenSecret=@2 WHERE Name=@0";

if (db.QuerySingle(SELECT, userName) == null) // この処理を追加してみました
{
db.Execute(INSERT, userName, accessToken, accessTokenSecret);
}
else
{
db.Execute(UPDATE, userName, accessToken, accessTokenSecret);
}
}

// CreateOrUpdate とか言ってるけど、
// やってることは Users テーブルと内部管理テーブルの紐づけ
OAuthWebSecurity.CreateOrUpdateAccount(
provider,
providerUserId,
userName);

// ログインチケットの発行
OAuthWebSecurity.Login(
provider,
providerUserId,
            createPersistentCookie: true);

Response.Redirect(returnUrl);
}
else
{
// ログインに失敗したときの処理
}
}

自分でプロバイダーを実装するのはそこはかとなくめんどくさいけれど、丸コピで動くのでまぁ、よし。プロバイダーをどうやって実装するのかも少し分かったし。練習として、ほかのサービスを実装してみるのもよいかもしれない。最近なんかだと GitHub なんかが需要ありそうだ。