Deno 用 Twitter OAuth 1.0a 認証URL 取得 ライブラリを作成

年末年始から Twitter OAuth 1.0a の仕様を調べていていました。
Deno の Twitter 向けライブラリは、開発者自身のアクセストークンを使うライブラリはちらほら見つかります。
ユーザーのアクセストークンを取得する、OAuth 1.0a の Using 3-legged OAuth 用の公開されたライブラリは見当たらなかったので、自前で作って公開しました。

(OAuth 2.0 用は、あった。OAuth2 Client for Deno)

参考にしたもの含めメモしておきます。

公開したライブラリ

2022年1月11日公開 Deno 向けで、Twitter API の中で 認証部分だけフォーカスしたライブラリ。
任意のAPIの利用は既にライブラリが割とあるので、認証部分だけ特にtwitterログインを作りたかったので作った。

deno.land/x で公開しているのはこちら。
deno.land/x/twitter_oauth_1_0a

以下メモになるので、「いいからソースとサンプルを見せろ」という方はgithubを見てもらった方がイイです。
以下ソースコードは有りません。

参考

公式

PHP JavaScript での実装例

その他

ユーザー情報取得までの流れ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST https://api.twitter.com/oauth/request_token?oauth_callback=XXXXXXXXXXXX トークンを取得
||
トークン取得
||
\/
GET https://api.twitter.com/oauth/authenticate?oauth_token=YYYYYYYYYYY` にリダイレクト
||
ユーザーが認証
||
\/
GET 指定されたコールバック URL へリダイレクト パラメーターを取得
||
\/
POST https://api.twitter.com/oauth/access_token アクセストークン取得
||
\/
任意の API を使用

作成した twitter_oauth_1_0a(deno_twitter_oauth) はこの中でアクセストークン取得までを担当。

実装

/oauth/request_token にアクセス

認証 - 署名の作成 が参考になるが、鵜呑みにできなかった。
アクセストークンの作成までに署名の作成タイミングが 2 回有ることに最初気が付かなくて、1 回目に特化したものを作って作り直す羽目になった。
反省でしかない。

署名は、署名ベースと署名キーを使うそれぞれの作成方法と含めるパラメータが大事。

/oauth/request_token の署名作成

キーベースに含む文字列は、次の手順で作成。

パラメータ名
oauth_consumer_key < 開発者の用意したコンシュマーキー >
oauth_timestamp < タイムスタンプ(数字 10 桁) >
oauth_nonce < ランダムな値 >
oauth_version 1.0
oauth_signature_method HMAC-SHA1
oauth_callback 認証後のリダイレクト先 URL

確認用にタイムスタンプは固定の値で試していたことも有ったが動作しているように見えた。
「現在の時刻」の値のリアルタイム性は、あまり重要でない可能性がある。

これを 次の手順で処理する。

  1. 署名のキーと値をパーセントエンコード(ただし、キーは文字列と_なのでエンコードしなくてよかった)
  2. キーのアルファベット順で並べ替えて
  3. [キー]=[パーセントエンコードした値]で文字列化
  4. 3. で作成した文字列を & を挟んで連結
  5. [大文字のHTTPメソッド]&[パーセントエンコードしたリクエスト先URL]&[パーセントエンコードした4.] で作成完了。

作ったものは、次のようになる。

POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Doob%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_version%3D1.0"

署名キーは、次のパラメータで作成。

パラメータ名
oauth_consumer_secret < 開発者の用意したコンシュマーシークレット >
  1. [コンシュマーシークレット]& を作成

  2. HMAC-SHA1 アルゴリズムで署名

  3. 署名完成

作ったものは次のようになる。
KJmaxYxSHztR8Our3DFAqE2xBgw%3D

/oauth/request_token のヘッダーを作成

ヘッダーで要求されるパラメーターは次の通り。

パラメータ名
oauth_consumer_key < 開発者の用意したコンシュマーキー >
oauth_timestamp < タイムスタンプ(数字 10 桁) >
oauth_nonce < ランダムな値 >
oauth_version 1.0
oauth_signature_method HMAC-SHA1
oauth_callback 認証後のリダイレクト先 URL
oauthSignature 先に作った署名
  1. キーのアルファベット順で並べ替えて
  2. [キー]=[値]で文字列化
  3. 10. で作成した文字列を = を挟んで連結
  4. 11. で作成した文字列を , を挟んで連結
  5. OAuth [12. で作成した文字列] を作成

この操作で、OAuth xxxxxxxxx~~ の文字列が作成できるので、これをヘッダーにつけてリクエストする。

この返り値に次の3つのパラメーターが返ってくる。

パラメーター 用途
oauth_token ユーザーを twitter 画面にリダイレクトさせる際にパラメータにつける
oauth_token_secret アクセストークンを取る際に使うので一旦保存しておく(セッションなどでよい)
oauth_callback_confirmed true/false が返ってくるが今回は使わなかった。

後は、https://api.twitter.com/oauth/authenticate?oauth_token=[oauth_token] の形にして、この URL へリダイレクトさせる。

/oauth/access_token にアクセス

Twitter 画面で認証すると、指定したコールバック URL に返ってくるので、次はアクセストークンを取得する。
作成方法は変わらないが、パラメーターが変わるので、以下の通り示す。

/oauth/access_token の署名作成

署名ベース作成のパラメーターは次の通り。

パラメータ名
oauth_consumer_key < 開発者の用意したコンシュマーキー >
oauth_timestamp < タイムスタンプ(数字 10 桁) >
oauth_nonce < ランダムな値 >
oauth_version 1.0
oauth_signature_method HMAC-SHA1

署名キー作成のパラメーターは次の通り。

パラメータ名
oauth_consumer_secret < 開発者の用意したコンシュマーシークレット >
oauth_token_secret

キーは、6. [コンシュマーシークレット]&[OAuth トークンシークレット] の形で作成。

/oauth/access_token のヘッダー作成

ヘッダー作成のパラメーターは次の通り。

パラメータ名
oauth_consumer_key < 開発者の用意したコンシュマーキー >
oauth_timestamp < タイムスタンプ(数字 10 桁) >
oauth_nonce < ランダムな値 >
oauth_version 1.0
oauth_signature_method HMAC-SHA1
oauth_callback 認証後のリダイレクト先 URL
oauthSignature 先に作った署名
oauth_token < /oauth/request_token のレスポンスで取得したもの>
oauth_verifier < コールバック URL にアクセスの差異に付与されているパラメーター>

これを使って、https://api.twitter.com/oauth/access_token にアクセスすると認証したユーザーとして操作できるアクセストークンを入手できる。

結構気にしたこと

変数なるべくキャメルケースで書きたいなぁ

最終的に oauth_token とかに変形させたりするのが見えていたもののできるだけキャメルケースで書けるようにした。
しかし、戻ってきた値を動的にキャメルケースの形で詰め込みなおすのを断念したのが心残り。
TSだからか、動的にプロパティを生やす消すをし難いと感じたが、もうちょっと頑張ればできそうな気もする。

ちゃんとテスト書きたいなぁ

Deno.test をちゃんと使ってテストを今回は書いてみた。
しかし、どうしてもリダイレクトしたりAPIアクセスがあるのですべてを書けたわけではないのが心残り。
このあたりスタブを用意できると解決しそうなので用意してみたい。


派生形のような形で PIN ベース認証というものも作れるようなので、ここまではアップデートを継続しようかと考えているところ。

ではでは。