スキップしてメイン コンテンツに移動

Twitter APIをcurlで呼び出す場合のSignatureの生成

学習の一環として、Twitter APIを既存のライブラリなどを使用せずにLow Level(curlなど)で呼び出して実行していた。
このときに些細な点で1-2時間ほど時間を溶かしてしまったので記録しておく。

先に3行でまとめておくと

  • CLIなどコマンドベースでTwitterの認証トークンを使用する場合はPIN Based OAuthを使う

  • 取得したKeyやTokenを使用する場合にはSignatureと呼ばれる、データの完全性を保証するためのHMAC-SHA1形式の暗号データを作る必要がある

  • これをコマンドで作る場合には echo -n "value" | openssl dgst -sha1 -binary -hmac "key" | base64 のようにする

使用するAPIとドキュメント

基本的には公式ドキュメントを読めばできる、とまでは言えないが、できるちょっと手前まではそれなりにしっかり書いてあった。

今回主として見る必要があったのは次の4つ。PINベースの認証の中で後の3つのAPIをそれぞれ呼ぶ。


アクセストークンを取得するまでのつまづきポイント

少しつまづいたのはcurlでのURLの記載方法。
call_backにoobを記載すれば次のステップの時にリダイレクトされない、と書かれていたのだけれど、実際にはリダイレクトされてしまって困った。
最終的には、URLを""で囲むことで正しく処理されたのだけど、そこは正しく解釈されるだろうと思い込んでしまっていたのが盲点だった。

curl --request POST \
--header "Authorization: Bearer AAAAAAAAxxxxxxxx" \
--url "https://api.twitter.com/oauth/request_token?oauth_consumer_key=sxW9FQEHduKj3u5y2xxxxxxxx&oauth_callback=oob"

この処理がうまくいくと、後で使用するTokenやSecretが応答される。
この後PINコードを取得するため、Webページを開くのだけれどここも地味につまづいた。

開発者がここでやらなければいけないことは、↑の処理で得た oauth_token を付け加えた形で、次のURLに誘導する(言い換えるとブラウザで開かせる)必要があるということ。($oauth_tokenの部分は実際の値に置き換え)

https://api.twitter.com/oauth/authorize?oauth_token="$oauth_token"

curlでこのURLを呼んだらアクセスするべきURLが応答されるような振る舞いだと勝手に想像してしまっていたのが要因。
頑張ってcurlで呼んでいたら、明らかにHTMLのページが応答されていて、期待と違うので直接アクセスしてみたら解決した。これもわからないとわからないまま唸ってしまう気がする。

これで無事PINコードが取得できたら公式ドキュメントどおりoauth_verifierにPINをセットすれば、ユーザーに代わって操作するために使用するSecretなどが手に入る。

アクセストークンを使用した操作

Keyはともかく、Secretはそのままクエリパラメーターに使用したり、Bodyの中に入れたりするわけにはいかないので、Signature(署名)という形で対応することで認証の解決を図る。

作り方は少し複雑に感じるかもしれない。Twitter曰く、多くの言語ではこれを生成するためのライブラリやツールがあるのでそれを使うのが良い、とのこと。

今回は自分で手打ちしたかったので下記を参照して生成した。

要は、アクセス先のURL、アプリのトークン、シークレット、アクセス用のトークン、シークレット、などなどを記載の通りくっつけていけばいいという話。

URLエンコーディングのツールと opensslコマンド、base64コマンドが使えたらできる。

詳しくは上記URLを見るとして、こんな感じの仕上がりのものを作る。

POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fdestroy%2Fxxxxxxx.json&oauth_consumer_key%3DsxW9FQEHduKj3u5y2xxxxxxxx%26oauth_nonce%3Dxxxx%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1646244000%26oauth_token%3D12239319102xxxxxxxx-xxxxxxxxxxxx%26oauth_version%3D1.0

そして、手順に従って↑をデータ、アプリのシークレットとアクセス用のシークレットをくっつけたものを鍵としてハッシュ化してからbase64でエンコードする。

コマンドラインではこんな感じでできる。

echo -n "POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fdestroy%2Fxxxxxxx.json&oauth_consumer_key%3DsxW9FQEHduKj3u5y2xxxxxxxx%26oauth_nonce%3Dxxxx%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1646244000%26oauth_token%3D12239319102xxxxxxxx-xxxxxxxxxxxx%26oauth_version%3D1.0" | openssl dgst -sha1 -binary -hmac "xxxxxxxxxxxx&xxxxxxxxxxxx" | base64

上記の実行結果は WyT+l38PgTNyquFqHZCSqyWQqho=。(ちなみに、当たり前だけど1文字で違えば全く異なる生成結果になる)

この生成したSignatureを使って次のような形でリクエストをすることができる。

curl --url 'https://api.twitter.com/1.1/statuses/destroy/xxxxxxx.json' -X POST -H 'Authorization: OAuth oauth_consumer_key="ssxW9FQEHduKj3u5y2xxxxxxxx", oauth_nonce="xxxx", oauth_signature="WyT+l38PgTNyquFqHZCSqyWQqho=", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1646244000", oauth_token="12239319102xxxxxxxx-xxxxxxxxxxxx", oauth_version="1.0"'

応答結果は受け取ると少なくない量のデータが入っているので、不要ならtrimなどした方が良い。

成功に至るまでの失敗

ここからは軽く2時間ほど溶かしてしまった笑い話。

手順には従ったつもりあったのだけれど、次のようなエラーが出続けて肝心のTweet操作ができなかった。

{"errors":[{"code":215,"message":"Bad Authentication data."}]}

そしてその要因は、「HMAC-SHA1の生成値として使用していた値が異なっていた」というもの。

# binaryをつけないケース
$echo -n "value" | openssl dgst -sha1 -hmac "key"
57443a4c052350a44638835d64fd66822f813319

# ここで生成された値は実際には使えない
$echo -n "value" | openssl dgst -sha1 -hmac "key" | base64
NTc0NDNhNGMwNTIzNTBhNDQ2Mzg4MzVkNjRmZDY2ODIyZjgxMzMxOQo=

# binaryをつけている正しいケース
$echo -n "value" | openssl dgst -sha1 -binary -hmac "key"
WD:L#P�F8�]d�f�/�3 

# ここで生成された値はSignatureとして使用できる
$echo -n "value" | openssl dgst -sha1 -binary -hmac "key" | base64
V0Q6TAUjUKRGOINdZP1mgi+BMxk=

次のブログが提供してくれているcurlで実行する例の中にあるsignature生成用のスプレッドシートが生成してくれた値を使ったら無事API実行できたので、結果、何がおかしいのかに着目することができた。

同じことはここでも確認できる。
Hash and HMAC calculator

この次の記事が悪いわけでは全くないのだけど、安易にこの結果を見て早合点してしまったのが失敗だった。

curlでコードベース認証をし、そのままcurlでTweetを操作しようとする例はあまり需要のない話ということもあり例を見つけられず苦労したのでここに書き残しておく。