TwitterのOAuth2.0 Betaためしてみた!


本記事に記載の内容はBeta版当時の古い内容になります。
すでにTwitter OAuth2.0は正式提供されており、設定方法など変更されているため、最新のドキュメントを参照するようにしてください。
本記事を参考に設定などをおこなった場合、最悪アプリケーションに脆弱性を作り込むことになります。

※正式提供版における設定や動作については以下の記事として公開しております。

https://zenn.dev/kg0r0/articles/8b1cfe654a1cee

はじめに

TwitterがOAuth2.0のベータ版をリリースしたとのことで、ベータプログラムに参加して実際に利用してみました。
今回は動作を細かく確認したいのでライブラリを利用せずにcurlで実施しています。

認可リクエスト

まず認可リクエストを組み立て、ブラウザからアクセスします。
認可リクエストの各パラメーターについては、公式ドキュメントのParametersを参考にすると良いでしょう。


なお、今回は以下のような認可リクエストになりました。基本的にOAuth2.0に準拠したパラメーターなので詳細は省きますが簡単に補足します。
https://twitter.com/i/oauth2/authorize?response_type=code&client_id=<クライアントID>&redirect_uri=https://127.0.0.1:3000/auth/twitter/callback&scope=tweet.read%20users.read%20account.follows.read%20account.follows.write%20offline.access&state=abc&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=s256
  • response_type
    現状"code"固定のようです。

  • client_id
    ベータプログラムに参加することでDeveloper Portalから確認できるようになるので、その値を指定します。

  • redirect_uri
    OAuth1.0用に設定していたCALLBAK URLSの値が共通で利用されます。

  • scope
    定義されているスコープの中から必要なものを指定します。
    各エンドポイントに対して必要なスコープも公式のEndpointsに記載があります。

  • state
    CSRF対策用のパラメーターです。今回はサンプルなので推測可能な値を設定していますが、実際に利用する際はランダムな値を指定してください。

  • code_challenge
    code_verifierを後述するcode_challenge_methodで計算した値を指定します。
    公式のドキュメントに記載されていた以下のOnline PKCE Generatorを利用した場合は以下のようになります。

    なお、Code VerifierはRFC 7636の記載にもとづい43文字から128文字の範囲で指定します。
    今回はRFC 7636の「Appendix B. Example for the S256 code_challenge_method」に記載されている値をそのまま利用します。実際は推測不可能なランダムな文字列を利用するようにしてください。

  • code_challenge_method
    plains256が利用可能です。
    RFC7636のSecurity Considerationsにも記載がありますが、特別な理由がなければplainは利用すべきではないでしょう。

同意画面

認可リクエストのURLにアクセスすると以下のような同意画面が出ます。スコープにもとづいて権限が要求されているので、確認して[Authorize app]を押下します。

認可レスポンス

[Authorize app]を押下すると、以下のような事前に指定したリダイレクトURLにリダイレクトされます。

https://127.0.0.1:3000/auth/twitter/callback?state=abc&code=X2luZ2ZxTUxLWFhqTl9RYkpKWXUzSWhzOE1HbkMyV1hvd0wwWEJHX2dTUGp2OjE2MzEzNzA3Njg1Mzc6MToxOmFjOjE

URL中のcodeパラメーターが認可コードなのでアクセストークンを取得するために後ほど利用します。
また、今回は省いていますが、実際にはここでstateパラメーターを検証します。

アクセストークンリクエスト

さきほどの認可コードを利用して以下のようなリクエストを送信することでアクセストークンを取得します。

$ curl --location --request POST 'https://api.twitter.com/2/oauth2/token' \--header 'Content-Type: application/x-www-form-urlencoded' \--data-urlencode 'code=X2luZ2ZxTUxLWFhqTl9RYkpKWXUzSWhzOE1HbkMyV1hvd0wwWEJHX2dTUGp2OjE2MzEzNzA3Njg1Mzc6MToxOmFjOjE' \--data-urlencode 'grant_type=authorization_code' \--data-urlencode 'client_id=<クライアントID>' \--data-urlencode 'redirect_uri=https://127.0.0.1:3000/auth/twitter/callback' \--data-urlencode 'code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'
{
  "token_type": "bearer",
  "expires_in": 7200,
  "access_token": "<アクセストークン>",
  "scope": "offline.access account.follows.write users.read account.follows.read tweet.read",
  "refresh_token": "<リフレッシュトークン>"
}

注意点としては、認可コードの有効期限が30秒しかないので手動で実施しているとうっかり有効期限切れしてしまいます。また、現状クライアントに対してConfidentialまたはPublicの指定ができず、トークンエンドポイントに対して認証はかけられていないようなのでclient_secretの指定はありません。

APIリクエスト

取得したアクセストークンを用いてAPI仕様書に従ってリクエストすることで正常応答が得られます。

$ curl --location --request GET 'https://api.twitter.com/2/users/1338063849413472258' \--header 'Authorization: Bearer <アクセストークン>' | jq
{
  "data": {
    "id": "1338063849413472258",
    "name": "56 dev",
    "username": "kgoro_dev"
  }
}

なお、多くのAPIで指定する必要があるidは適当なサービスで検索できました。

また、リクエストに関しては、以下のGitHub Gistに色々まとめられていたので、公式と合わせて参照すると良いかもしれません。

https://gist.github.com/JessicaGarson/6c8f381bc9a365ac16c39427b120659b

アクセストークンの更新

認可リクエストにおいてscopeとしてoffline.accessを要求することで、アクセストークンリクエストのレスポンスとしてリフレッシュトークンを取得することができます。
リフレッシュトークンを利用して以下のようなリクエストをトークンエンドポイントに送信することでアクセストークンを更新することができます。

$ curl --location --request POST 'https://api.twitter.com/2/oauth2/token' \--header 'Content-Type: application/x-www-form-urlencoded' \--data-urlencode 'refresh_token=<リフレッシュトークン>' \--data-urlencode 'grant_type=refresh_token' \--data-urlencode 'client_id=<クライアントID>'
{
  "token_type": "bearer",
  "expires_in": 7200,
  "access_token": "<アクセストークン>",
  "scope": "offline.access account.follows.write users.read account.follows.read tweet.read",
  "refresh_token": "<リフレッシュトークン>"
}

なお、インプリシットグラントにおいてリフレッシュトークンを発行してはならないことからも、クライアント認証できないPublic Clientに対してリフレッシュトークンを発行するべきではないでしょう。

http://openid-foundation-japan.github.io/rfc6749.ja.html#implicit-authz-resp
基本的にリフレッシュトークンはSender Constrained Tokenであり、Confidential Clientに対して発行した場合、認可サーバーはクライアント認証してリフレッシュトークンが確かにそのクライアントに対して発行されたものであることを確認しなければならないことが仕様に明記されています。
http://openid-foundation-japan.github.io/rfc6749.ja.html#token-refresh
しかし、現状のTwitterの実装ではクライアント認証なしでリフレッシュトークンを利用することができるため、リフレッシュトークン漏洩時のリスクが高いと言えます。

おわりに

今回はTwitterのOAuth2.0 Betaを試してみました。
まだまだ気になる箇所や実装が足りていない?箇所がありますが、せっかくなのでフィードバックしていこうと思います。

参考