Contents
OAuth1.0a認証
Twitterは外部アプリなどを認証する際にOAuth1.0aという仕組みを利用している。
具体的に言えばConsumer KeyとConsumer Secretを使ってoauth/request_tokenのAPIを叩くことによってoauth_token、oauth_token_secretなどの認証に必要なトークンを取得し、正しいURLからアクセスされたことを確認したのちにアクセストークンを取得するという仕組みである。
Request Token
以下の参考文献でPHPのコードを読んでそれをSwiftで書き直したものになります。

api_keyとapi_secretはそれぞれConsumer KeyとConsumer Secretを指します。
let api_key: String = "" let api_secret: String = "" let callback_url: String = "" let request_url: String = "https://api.twitter.com/oauth/request_token" let request_method: String = "POST" let signature_key: String = api_secret.rawurlencode + "&" + access_token_secret.rawurlencode var headers: [String: String] = [ "oauth_callback" : callback_url, "oauth_consumer_key": api_key, "oauth_nonce": String(Int(Date().timeIntervalSince1970)), "oauth_signature_method": "HMAC-SHA1", "oauth_timestamp": String(Int(Date().timeIntervalSince1970)), "oauth_version": "1.0" ] var request_params: String = (headers.sorted(by: {$0.0 < $1.0}).map({ "\($0.key)=\($0.value.rawurlencode)&"}).reduce("", +)) request_params = String(request_params.prefix(request_params.count - 1)) request_params = request_params.urlencode let encoded_request_method: String = request_method.rawurlencode let encoded_request_url: String = request_url.rawurlencode let signature_data: String = encoded_request_method + "&" + encoded_request_url + "&" + request_params let signature: String = hmacsha1(message: Data(signature_data.utf8), key: Data(signature_key.utf8)) headers.updateValue(signature, forKey: "oauth_signature") let headers_params: String = headers.sorted(by: {$0.0 < $1.0}).map({ "\($0.key)=\($0.value.urlencode),"}).reduce("", +) let header: HTTPHeaders = [ "Content-Type": "application/json", "Authorization": "OAuth \(String(headers_params.prefix(headers_params.count - 1)))" ]
HMAC-SHA1を計算するにはCommonCryptoが必要なので忘れずにインポートしておきましょう。
パーセントエンコーディングを毎回と長々と書くのはめんどくさいのでExtensionを使って簡単に計算できるようにします。
func hmacsha1(message:Data, key:Data) -> String { var macData = Data(count: Int(CC_SHA1_DIGEST_LENGTH)) macData.withUnsafeMutableBytes { macBytes in message.withUnsafeBytes { messageBytes in key.withUnsafeBytes { keyBytes in CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), keyBytes, key.count, messageBytes, message.count, macBytes) } } } return macData.base64EncodedString() } extension String { var rawurlencode: String { return self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)! } var urlencode: String { let charset = NSCharacterSet(charactersIn:"&:=\"#%/<>?@\\^`{|}").inverted return self.addingPercentEncoding(withAllowedCharacters: charset)! } }
あとはAlamofireなりでPOSTリクエストを送ればText形式で以下のような形式の値が返ってきます。
oauth_token=5mb9VtYwa27HTVjK5OhoyyI503dWoPndDQ9G4V8yCI&oauth_token_secret=4dW4gGLic6oItvd0YySWRU5aLjBQsw1N9xDC3Wkqw&oauth_callback_confirmed=true
ここでoauth_tokenは認証用のURLを作成するのに必要で、oauth_token_secretは正しいURLからジャンプしたことをチェックするための仕組みです。
認証してみよう
https://api.twitter.com/oauth/authenticate?oauth_token={OAUTH_TOKEN}
のURLにアクセスすればおなじみのあの画面が表示されます。
ただし、これは一回ポッキリのURLなので二回目以降にアクセスしても無効なURLと表示されて認証することができません。
任天堂のAPIは何度も同じURLを使えるのに、セコいですな。
よく考えたらこんな機能は要らなかった
で、苦労して(四時間くらいかけて)頑張ってSwiftで移植したのはいいのですが、これを利用しようとすると、
認証用のURLを作成→それを変数に代入→認証用URLを開く→CallbackURLで戻ってくる
という流れになり、コールバックした際にURLSchemeでHookできるのであれば「わざわざ認証用URLをSwiftでつくる意味があるのか」ということになるわけです。
これなら、最初からアクセスすると認証用のURLにジャンプしてくれるようなページまたはAPIをつくっておいて、そこにアクセスしたら認証画面が開き、認証後にコールバックするような仕組みでもいいわけです。
面倒になるのは認証用のURLを作成するために別途PHPなりでサーバを立てなければいけないことですが、できないことはないのでやってみました。
PHPで認証用のサイトをつくる
というわけで、結局PHPで認証用のデモサイトをつくることにしました。
<?php require "vendor/autoload.php"; use Abraham\TwitterOAuth\TwitterOAuth; session_start(); define("TWITTER_API_KEY", API_KEY); define("TWITTER_API_SECRET", API_SECRET); define("CALLBACK_URL", CALLBACK); $connection = new TwitterOAuth(TWITTER_API_KEY, TWITTER_API_SECRET); $request = $connection->oauth("oauth/request_token", array("oauth_callback" => CALLBACK_URL)); $_SESSION["oauth_token"] = $request["oauth_token"]; $_SESSION["oauth_token_secret"] = $request["oauth_token_secret"]; $url = $connection->url("oauth/authenticate", array("oauth_token" => $request["oauth_token"])); header("Location: ".$url);
CALLBACKだけはダブルクオーテーションで挟んで文字列で書かないとダメですが、それ以外はベタコピーでオッケーです。
あとは適当にindex.phpとか名前をつけてアクセスするだけですね。
BetterSafariViewで開いてみる
Swiftのライブラリの一つであるBetterSafariViewにはWebAuthenticationSessionというSFSafariViewを開きつつ認証もしてくれるという便利な仕組みがあります。
デフォルトではGithubのOAuthのデモができるのですが、果たしてTwitterでもうまくいくのでしょうか?
すると、ちゃんとログインしてトークンをとってくることができました!!
ちなみに表示されてるoauth_tokenはinvalidなConsumer Keyから作成したのでもう使えないです。
ここから何ができるか
BetterSafariViewで認証ができたということは、SFSafariViewを使ってネイティブにSalmon Statsとの連携ができることを意味します。
今まではWebKitをつかってWebViewを作成し、そこでログインしたあとでWebViewのCookieを全部取得してその中からlaravel_sessionを探し、あればそのlaravel_sessionを使ってSalmon Statsからリザルトのアップロードに必要なapi-tokenを取得していました。
ただ、WebKitがSwiftUIからは標準サポートされておらずいろいろとバグが多いのでなんとかネイティブにできないかと考えていたわけです。
その点、SFSafariViewはSafariと同じように扱えるので便利なのですが「Cookieなどの情報がSafariと共有されないくせにSafariのインスタンスとして扱われる」のでアプリ側からCookieにアクセスできないという欠点がありました。
なのでこうしてネイティブにトークンを扱える仕組みが必要だったわけです。

さて、ここまででoauth_tokenとoauth_verifierが取得できたので次はここからアクセストークンを取得する必要があります。
これは割と簡単そうなので、ここまでできればもう一歩という感じはしますね。
自身を天才と信じて疑わないマッドサイエンティスト。二つ上の姉は大英図書館特殊工作部勤務、額の十字架の疵は彼女につけられた。
コメント