【備忘録】Twitter API v2 OAuth2.0 コマンドプロンプトからTwitter を使ってみる

意識低い系新人プログラマの備忘録。
## ◆この記事でやること
Twitter API での認証・認可方法について大雑把に説明します。

Twitter API v2 でOAuth2.0を使って
アプリケーション認証、アカウント連携、ツイート取得、ツイートの投稿 などを行います。

Twitter API の仕組み自体を学ぶことを目的とし、コマンドプロンプトを使用します。
プログラミング言語は使用しません。

## ◆環境
- Windows10
- Command Prompt

- [Twitter Developer Portal](https://developer.twitter.com/en/portal/dashboard) の User authentication settings

    - Twitter API v2 Elevated の審査に通過済
      ※今回の内容ではEssentialレベルでも支障はないと思われる
    - OAuth2.0とOAuth1.0aの両方を有効化
      ※今回の記事ではOAuth2.0だけでも支障はない

    - Type of App
　confidential clientsを選択
　Wep App または Automated App or bot を選択

    - App permissions
　Read and Write を選択

## ◆筆者個人の目標
- 勉強のため、Twitter APIを使って簡単なツイッタークライアントアプリをC#で作りたい。
- そのためにまずは Twitter API でのOAuthの使われ方をふわっとでも理解しておきたい。
- なるべく後発の認可プロトコル(OAuth2.0)を使用したい。

## ◆Twitter API での OAuth ふわっと概要

#### ① OAuth1.0a
- ユーザーのログイン状態に「近い」状態で、読み取り・書き込みを行う。
- APIKey、APISecret、AccecssToken、AccessTokenSecret の4つの組み合わせで認証を行う。

※「近い」とは
　アプリケーションは、利用者本人が行える全ての操作を行えるとは限らない。
　認可された権限内に限り、利用者本人に代わって操作ができる。

※今回は OAuth2.0 に焦点を当てるのでこれ以上の詳細は省く。

#### ② OAuth 2.0 *App-only*
- App-only とは、アプリケーション単独認証である。
- 読み取り専用。ログアウト状態でもアクセス可能な情報の取得ができる。
　(例) 鍵なしアカウントのツイートを取得する etc

- 書き込みをはじめとする、ログイン状態でないとできないような操作はできない。
　(例) ツイートを投稿する、いいねする、リツイートする、鍵つきフォロイーのツイートを取得する etc 
- Bearer Token のみを使用して認証を行う。
- AccessToken として Bearer (というタイプの) Token を使用している。
- この Bearer Token には、自ら破棄するまで有効期限はないっぽい。
- この Bearer Token は Developer Portal から破棄・再発行できる。

#### ③ OAuth 2.0 *Authorization Code Flow with PKCE*
- Authorization Code Flow with PKCEとは、PKCE(ピクシー)を使用した認証コードフローである。

- 「OAuth1.0aではできるがOAuth2.0(App-only)ではできない事」のほとんどをカバーする。
例：ツイートを投稿する、いいねする、リツイートする、鍵つきフォロイーのツイート情報を取得する etc

- PKCEは、OAuth2.0の拡張仕様である。
- PKCEは、認可コード横取り攻撃への対策を目的とし、RFC7636で定義されているものである。
かなり大雑把に言えば、書き込み等が「安全に」行える。
  Twitter API では OAuth 1.0a の後継のような存在。
- Twitterにログインしている状態で、Client ID と Client Secret を使用して認証を行う。
- 認証されると、ユーザー個別の Access Token と Refresh Token が発行される。
- この Access Token の有効期限は、7200秒 (=2時間) または期限内に自ら破棄するまで。
- Access Token を更新するとき、Refresh Token を使用して新しい Access Token を発行する。
(Refresh Token 紛失時は、アプリ連携からやり直すしかない感じか？※未検証)

:::note warn 
#### Bearer Token および Access Token について

- [Wikipedia](https://ja.wikipedia.org/wiki/Bearer%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3)によると、「OAuth2.0は Bearer Token による認証/認可をサポートしている」とある。
- [こちらの書籍](https://www.amazon.co.jp/%E9%9B%B0%E5%9B%B2%E6%B0%97%E3%81%A7%E4%BD%BF%E3%82%8F%E3%81%9A%E3%81%8D%E3%81%A1%E3%82%93%E3%81%A8%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%EF%BC%81%E6%95%B4%E7%90%86%E3%81%97%E3%81%A6OAuth2-0%E3%82%92%E4%BD%BF%E3%81%86%E3%81%9F%E3%82%81%E3%81%AE%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%AC%E3%82%A4%E3%83%89-%E6%8A%80%E8%A1%93%E3%81%AE%E6%B3%89%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%EF%BC%88NextPublishing%EF%BC%89-Auth%E5%B1%8B-ebook/dp/B07XT8H2YG "雰囲気で使わずきちんと理解する！整理してOAuth2.0を使うためのチュートリアルガイド (技術の泉シリーズ（NextPublishing）)")には、「OAuth2.0 のアクセストークンは Bearer トークンです。」とある。
- Bearer Token の仕様を定めた [RCF6750](https://datatracker.ietf.org/doc/html/rfc6750)のタイトルは「OAuth 2.0認証フレームワーク：ベアラートークンの使用」となっており、「この仕様では、OAuthアクセストークンがベアラートークンである場合に保護されたリソース要求を行う方法について説明します。」とある。

- Bearer Token は、持参人トークン・署名なしトークンと呼ばれる。
例えるなら、電車の切符と同様で、本人確認は行われず、
買った人でも拾った人でも誰でもいいから所持さえしていれば権限を持つ点が特徴。

- Twitter API 公式ドキュメントやネット記事で説明される **Access Token** には下記の3種類がある。

**①OAuth2.0でアプリケーション単独認証に使う Access Token**
- Twitter 公式での主な呼ばれ方は、 Bearer Token、OAuth 2.0 App Access Token など。
- OAuth2.0 App-only で使用するもの。
- アプリケーションにつき1つしか存在しない。
- 破棄するまで有効期限はなさそう。

**②OAuth2.0+PKCEでユーザー認証に使う Access Token**
- Twitter 公式での主な呼ばれ方は、 Access Token、OAuth 2.0 user Access Token など。
- OAuth2.0 Authorization Code Flow with PKCE で使用するもの。
- アプリケーションを利用するユーザーに紐づき、その数だけ存在する。
- 有効期限は7200秒(=2時間)。

**③OAuth1.0aで使用する Access Token**
- Twitter 公式での主な呼ばれ方は、 Access Token など。
- OAuth1.0aで使用するもの。
- アプリケーションを利用するユーザーに紐づき、その数だけ存在する。
- Developer Portal から取得、破棄、生成できるAccess Tokenがこれ。
(Developer登録に使用した自分のTwitterアカウントに紐付けられたもの)
- 破棄するまで有効期限はなさそう。

:::

まじ混同不可避！！！

## ◆概要まとめと補足

- **OAuth1.0a**…ログイン状態のような読み取り・書き込みができる。
- **OAuth 2.0 *App-only*** …ログアウト状態での読み取りのみができる。
- **OAuth 2.0 *Authorization Code Flow with PKCE***…ログイン状態のような読み取り・書き込みが安全にできる。

**ただしこれらはすべてアプリケーションが認可を受けた権限の範囲に限る。**

悪意のあるアプリではないことを利用者に信用してもらうためには、
アプリ開発者が必要最小限の権限だけ要求するのが良い。


## ◆コマンドプロンプトからTwitter をしてみる！
curlコマンドで Twitter API を呼び出し、Twitter を使ってみます。
***
### <font color="#1e90ff">1. <br>OAuth 2.0 App-only<br>アプリケーション単独認証を試してみよう！</font>
<font color="#1e90ff">準備：アプリケーションの Bearer Token</font>
***
#### 1-1. 特定のユーザーのユーザーIDを取得する
下記コマンドのパラメータを埋め、コマンドプロンプトで実行する。
```text:Request
curl -X GET https://api.twitter.com/2/users/by/username/【@xxxxxの@を除いた部分】
-H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" 
-H "Authorization: Bearer 【 アプリケーションの Bearer Token 】" 
```
すると、下記のようなレスポンスが返る。
```text:Response
{
	"data": {
		"id": "【 ユーザーID 】",
		"name": "【 表示名 】",
		"username": "【 @xxxxxの@を除いた部分 】"
	}
}
```
ユーザーIDとは、よく我々がIDと呼んでいる@xxxxxのことではなく、
レスポンスのidパラメータで帰ってくる18~19桁の数字のことです。

***
#### 1-2. 特定のユーザーの投稿を取得する
下記コマンドのパラメータを埋め、コマンドプロンプトで実行する。
【 ユーザーID 】には先ほどのレスポンスで取得したものを指定する。
```text:Request
curl -X GET https://api.twitter.com/2/users/【 ユーザーID 】/tweets 
-H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" 
-H "Authorization: Bearer " 【 アプリケーションの Bearer Token 】"
```

すると、下記のようなレスポンスが返る。
```text:Response
{
	"data": [
		{
			"id": "【 ツイートID 】",
			"text": "【 ツイート本文 】"
		},
		{
			"id": "【 ツイートID 】",
			"text": "【 ツイート本文 】"
		},

                  (中略)

	],
	"meta": {
		"next_token": "【 次のページをエンコードするトークン 】",
		"result_count": 10,
		"newest_id": "【 ツイートID 】",
		"oldest_id": "【 ツイートID 】"
	}
}
```
***


### <font color="#1e90ff">2. <br>OAuth 2.0 Authorization Code Flow with PKCE<br>PKCEを使用したOAuth2.0認証コードフローを試してみよう！</font>
<font color="#1e90ff">準備：Client ID、Client Secret</font>
***
#### 2-1. アカウントをアプリと連携させる

**(1) 認証リクエストURL生成**
メモ帳などで下記のパラメータを埋め、URLを作る。
```text:認証リクエストURL
https://twitter.com/i/oauth2/authorize?
response_type=code
&client_id=【 Client ID 】
&redirect_uri=【 Call back URI 】
&scope=tweet.read%20tweet.write%20users.read%20offline.access
&state=【 random value 】
&code_challenge=【 ★2 】
&code_challenge_method=【 ★1 】
```
:::note
パラメータの説明

・`redirect_uri` Developer Portal で指定した Callback URI。
・`scope` アプリケーションに与える権限の範囲。
・`state` 任意のランダムな値。

★1 `code_challenge_method` **plain** または **S256** を指定。
     推奨はS256だが、お試しの場合は手間のかからない plain がおすすめ。

★2 `code_challenge`

・事前準備として、43～128桁の任意の文字列を用意する。
これを <font color="red">Code Verifire</font> とする。（後でまた使う）

・code_challenge_method=**plain**の場合、
`code_challenge`=【<font color="red">Code Verifire</font>】
とする。

・code_challenge_method=**S256**の場合、
`code_challenge`=【<font color="red">Code Verifire</font>のSHA-256 のハッシュ値を BASE64-URL エンコードした値】
とする。
:::

<br>

**(2) Authentication Code (認可コード)を取得**

～流れ～
1. 連携したいアカウントで Twitter にログインしておく。
2. 上で作った認証リクエストURLをブラウザに打つ。
3. 許可を求める画面が出てくるので許可する。
4. 下記のような Call back URI に飛ぶ。

```text:Call back URI
【 Callback URI 】?state=【 random value 】&code=【 Authentication Code 】
```
<br>

**(3)ユーザー個別の Access Token 取得**
下記コマンドのパラメータを埋め、コマンドプロンプトで実行する。
- `code_verifire`パラメータには認証URL生成時に説明した <font color="red">Code Verifire</font> を指定する。
- 上の Call back URI で取得した codeパラメータの値【 Authentication Code 】を、
  30秒以内に下記のcurlコマンドの`code`パラメータにセットし実行。

```text:Request
curl -X POST https://api.twitter.com/2/oauth2/token 
--basic -u "【 Client ID 】:【 Client Secret 】" 
--header "Content-Type: application/x-www-form-urlencoded" 
-d "grant_type=authorization_code" 
--data-urlencode "redirect_uri=【 Call back URL 】" 
-d "code_verifier=【 Code Verifier 】" 
-d "client_id=【 Client ID 】" 
-d "code="【 Authentication Code 】" 
```
```text:Response (成功時)
{
	"token_type": "bearer",
	"expires_in": 7200,
	"access_token": "【 ユーザー個別の Access Token 】",
	"scope": "tweet.write users.read tweet.read offline.access",
	"refresh_token": "【 ユーザー個別の Refresh Token 】"
}
```
ユーザー個別の Access Token を取得できた。
Access Token と Refresh Token は控えておく。

```text:Response (認証コード期限切れで失敗時)
{
	"error": "invalid_request",
	"error_description": "Value passed for the authorization code was invalid."
}
```
今回は手動で試しているので30秒に間に合わない場合がある。
失敗したら、認証リクエストURLを新たに生成するところからやり直し。
次こそ30秒以内にコマンドを実行する。
<br>
***
#### 2-2. アプリ連携済のアカウントでツイートする
下記コマンドのパラメータを埋め、コマンドプロンプトで実行する。
【ユーザー個別の Access Token 】は上で取得したものを入力する。
```text:Request
curl -X POST https://api.twitter.com/2/tweets 
-H "Authorization: Bearer 【ユーザー個別の Access Token 】" 
-H "Content-type: application/json" 
-d "{\"text\": \"Hello, Twitter Land!\"}"
```
すると、下記のようなレスポンスが返る。
```text:Response
{
	"data": {
		"id": "【ツイートID】",
		"text": "Hello, Twitter Land!"
	}
}
```
***
#### 2-3. ユーザーのアクセストークンを再取得する
2時間の期限切れまたは前もって更新する場合に行う。

下記コマンドのパラメータを埋め、コマンドプロンプトで実行する。
```text:Request
curl -X POST https://api.twitter.com/2/oauth2/token 
--basic -u "【 Client ID 】:【 Client Secret 】" 
--header "Content-Type: application/x-www-form-urlencoded" 
-d "grant_type=refresh_token" 
--data-urlencode "refresh_token=【 ユーザー個別の Refresh Token 】"
```
すると、下記のようなレスポンスが返る。
```text:Response
{
	"token_type": "bearer",
	"expires_in": 7200,
	"access_token": "【ユーザー個別の新しい Access Token 】",
	"scope": "tweet.write users.read tweet.read offline.access",
	"refresh_token": "【ユーザー個別の新しい Refresh Token 】"
}
```

★その他の書き込み系「いいねする」、「リツイートする」等もこれと同様の流れでできる。
暇な時に追記予定。

***
### <font color="#1e90ff">3. <br>OAuth1.0aでしかできないこと</font>
***
OAuth 2.0 App-onlyとOAuth 2.0 Authorization Code with PKCEで
OAuth1.0aでできることを全てカバーできているのかと思いきやそうではないようです。(2022年4月現在)

OAuth1.0aでしかできないことの代表格が「ホームタイムラインを取得する」です。

OAuth 2.0では、特定のユーザーのツイートタイムラインは取得できるようなのですが
(GET /2/users/:id/tweets と GET /2/users/:id/mentions)

自身のフォロイー達のタイムラインを取得することはできないようです。
(GET /2/home 的なものは存在していないっぽい)

[API reference index](https://developer.twitter.com/en/docs/api-reference-index)や[Twitter API v2 authentication mapping](https://developer.twitter.com/en/docs/authentication/guides/v2-authentication-mapping)に記載がないので恐らくそうだと思っています…。


一方、OAuth1.0aで「ホームタイムラインを取得する」コマンドは下記のように始めるのですが…
```text:Request
curl -X GET https://api.twitter.com/1.1/statuses/home_timeline.json?
・・・・・(めっちゃ長いので略)・・・・・
```

OAuth1.0aのcurlコマンドは非常に複雑らしいです(まるで他人事)。[参考](https://akiblog.tech/?p=1509)

筆者はOAuth1.0aでしかできないことは**CoreTweet**というC#のパッケージライブラリを使って楽することにしました。[参考](https://pg-life.net/csharp/coretweet)
CoreTweetでは、ソースを見た感じ全てOAuth1.0aを使っているようです。
つまりOAuth2.0には非対応で、OAuth2.0でしかできないことはCoreTweetではできなさそうです。
ただOAuth2.0ではリクエストを簡潔に書けるようになったのでライブラリに頼るまでもなく実装できそうですね。


## ◆まとめ
・OAuth 2.0(+PKCE)は、OAuth1.0aのジェネリック要員。
　ただし、まれにOAuth1.0aでしかできないこともあるらしい。

・OAuth 2.0(+PKCE)では、OAuth1.0aと比較してかなり楽にリクエストを出せる。

備忘録感が満載なので、もし誤り等が判明したらすぐ直します。

## 参考
https://developer.twitter.com/en/docs/authentication/oauth-2-0/user-access-token
https://developer.twitter.com/en/docs/twitter-api/tweets/manage-tweets/api-reference/post-tweets
https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code