X APIを使用する

**OAuth 2.0 Flow with PKCE** を使用して、X APIを利用できるようにした時の学びを備忘録として残そうと思います。

本記事では以下をカバーします。
- X API使用のためのTwitter Developerアカウントの登録とセットアップ
- OAuth2.0 Flow with PKCEを使用し、X APIのアクセストークンを取得
- ターミナルからXへツイートする

*Xのアカウントはある前提です。

## 事前準備
以下の手順は、XのAPIを初めて使う方向けです。

まず、[**X Developer Portal**](https://developer.x.com/en/portal/dashboard) を開き、Twitter Developerアカウントを登録をします。

![スクリーンショット 2025-05-04 15.44.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173182/55145581-efb1-427c-8f5b-33ca89760efe.png)

**Basic**タブを開き、下の方に**Sign up for Free Account**があるのでそれをクリックします。

次に、X APIを使う理由を書き、規約に同意をします。
理由は最低250文字必要で日本語でも大丈夫かと思いますが、英語に機械翻訳した方が文字数を稼げますので、英語をお勧めします。

![スクリーンショット 2025-05-04 15.46.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173182/d67ba955-7af3-4a98-aba3-f59705894bcb.png)


登録が完了するとポータルサイトが開きます。
左メニューのProjects & Appsを開き、その中からプロジェクトをクリックするとこのような詳細ページが開きます。
ページ下部に「User authentication settings」があるので、**Set up**をクリックします。

![スクリーンショット 2025-05-04 15.59.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173182/7e88a0d1-3589-49af-b4db-321d2ef4974e.png)

ユーザー認証設定ページが開くので、以下の通り設定してください。
**App permissions:** Read and write
**Type of App:** Native App
![スクリーンショット 2025-05-04 16.12.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173182/d191c109-dcfb-4f58-b5b0-675121e1b0d4.png)


**App Info:**
以下2つが必須なので、両方入れてください。
- Callback URI / Redirect URL（認可コードを受け取るURLになります）
- Website URL
<sup>*どちらもテストだけであれば、ダミーのURLでもOKです。

![スクリーンショット 2025-05-04 16.13.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173182/d1235474-b034-412b-b5ff-77cd2413d1a7.png)

上記設定ができたら、Saveをクリックして保存します。
そうすると`ClientID`、`Client Secret`が表示されると思いますので、メモ帳などに保存しておいてください。
<sup>*Client Secretに関しては忘れた場合、後から確認ができず、再生成しないといけないので注意です。

![スクリーンショット 2025-05-04 16.16.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173182/3db1dc6b-f1f9-418a-a06d-16da0ed2f964.png)

次に、ターミナルを使って、`ClientID`、`Client Secret`を`:`で繋いだ文字列を `BASE64`にエンコードします。
```
echo -n 'CLIENT_ID:CLIENT_SECRET' | base64
```
すると、こんな文字列が生成されると思いますので、実践パートで使うので控えておきます。
```
Ykd4------省略------4MTNkZQ==
```

**BASE64とは？**
64進数を意味する言葉で、すべてのデータを62種類のアルファベット(a~z, A~Z)と数字(0~9)、一２種類の記号(+,/)の**64文字**で表すエンコード方式。

 ---

これで事前準備は完了です。

## OAuth 2.0 Flow with PKCEを使用し、アクセストークンを取得する

### Step 1 認可リクエスト

<sup>*この認可リクエストが成功すると、認可Codeが返却されますが、この認可Codeの有効期間は30秒と短いので、あらかじめ次のStep2のトークンリクエストのCurlコマンドを用意しておいてください。

認可URLの以下2つの項目に、ご自身の情報をセットしてブラウザで開いてください。
・CLIENT_ID
・REDIRECT_URI（Developer Portalで登録したCallback URI / Redirect URLと必ず同じ！）

`
https://twitter.com/i/oauth2/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&scope=tweet.read%20tweet.write%20users.read%20offline.access&state=state&code_challenge=challenge&code_challenge_method=plain
`

URLにアクセスすると以下のような認可画面が開きますので、**アプリにアクセスを許可(英：Authorize App）** をクリックして認可します。
<sup>*未ログイン状態ならログイン後にこの画面が開きますが、もし開かない場合はもう一度上記のURLを開いてください。

![スクリーンショット 2025-05-05 19.02.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173182/16915466-81fe-4dcb-88af-4349221add38.png)

認可を実行すると、指定したリダイレクトURLへ遷移して、認可Codeが返却されます。

認可CodeはURLの中に含まれます。
`https://www.example.com/?state=state&code=VGNibzFWSWREZm01bjN1N3dicWlNUG`


### Step 2 トークンリクエスト
Step 1で返却された認可Codeを使って、30秒以内にトークンのリクエストをします。
ターミナルで、以下のCurlを実行してください。
```
curl --location --request POST 'https://api.twitter.com/2/oauth2/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic <BASE64に変換したClientIDとClient Secretの文字列>' \
--data-urlencode 'code=<Step 1で返却された認可Code>' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'redirect_uri=<Developer Portalで登録したCallback URI / Redirect URL>' \
--data-urlencode 'code_verifier=challenge' \
--data-urlencode 'client_id$<CLIENT_ID>'
```

成功した場合、アクセストークン、リフレッシュトークンがレスポンスで返却されます。
```
{
  "token_type":"bearer",
  "expires_in":7200,
  "access_token":"XXXXXXX",
  "scope":"tweet.write users.read tweet.read offline.access",
  "refresh_token":"YYYYYYYYY"
}
```
<sup>*30秒以上経って失敗してしまった場合は、再度Step1の認可リクエストをやり直してください。

これでユーザーに代わって、Xに投稿したりすることができるようになりました。

## X APIにPOSTリクエストする
取得したアクセストークンを使って、XツイートAPIにPOSTリクエストを投げてみます。
```
curl -X POST 'https://api.twitter.com/2/tweets' \
--header 'Authorization: Bearer <アクセストークン>' \
-H 'Content-Type: application/json' \
-d '{"text": "Hello World 2025/5/4 Sunday!"}'
```

成功した場合、以下のようなレスポンスが返ります。idがツイートIDです。
```
{
  "data":{
    "id":"1918935953508556834",
    "edit_history_tweet_ids":["1918935953508556834"],
    "text":"Hello World 2025/5/4 Sunday!"
  }
}
```
x.comのアプリ側でも実際に投稿されているか確認して見ましょう。

## OAuth スコープ
Step1の認可リクエストのURLです。
`
https://twitter.com/i/oauth2/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&scope=tweet.read%20tweet.write%20users.read%20offline.access&state=state&code_challenge=challenge&code_challenge_method=plain
`

OAuthスコープは、`scope=tweet.read%20tweet.write%20users.read%20offline.access`の部分で、認可時にアプリが必要とする権限を定義しています。認可リクエストと共にこのスコープ情報が送信されます。

- tweet.read：ツイートの参照
- tweet.write：ツイート、リツイートの実行
- users.read：アカウントを参照できる
- offline.access：
X APIのオフラインアクセス、つまり、ユーザーがログインしていない状態でもAPIを利用するためにリフレッシュトークンを使えるようにする。
<sup>リフレッシュトークンは、有効期限が長く、アクセストークンを自動的に更新することができます。

[公式ドキュメントのScopes](https://docs.x.com/resources/fundamentals/authentication/oauth-2-0/authorization-code#scopes)

ユーザはスコープの許可を与えるかどうかを選択できるので、認可の画面にも以下のように表示されます。

![スクリーンショット 2025-05-05 19.02.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173182/16915466-81fe-4dcb-88af-4349221add38.png)

- **このアプリが実行できる処理**
    - あなたの代わりに投稿したり、再投稿したりする。=tweet.write
    - アクセス権を取り消すまでアカウントにアクセスできます。=offline.access
- **このアプリが参照できる内容**
    - あなたが表示できるすべての投稿（非公開アカウントの投稿を含む）です。=tweet.read
    - あなたが表示できるすべてのアカウント（非公開アカウントを含む）=users.read

## 認可コード横取り攻撃とPKCEの仕組み
認可コードを悪意のあるアプリに受領されると、利用者になりすましてアクセス権限を行使されてしまう**認可コード横取り攻撃**が可能になってしまう。

それを防ぐために、**PKCE（Proof Key for Code Exchange）** を使います。
PKCEは「アクセストークンを安全にやり取りするための、OAuth 2.0の拡張仕様」で、認可コードグラントフローのセキュリティを強化します。

Step1の認可リクエストのURLです。
`
https://twitter.com/i/oauth2/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<REDIRECT_URI>&scope=tweet.read%20tweet.write%20users.read%20offline.access&state=state&code_challenge=challenge&code_challenge_method=plain
`

`code_challenge=challenge`
`code_challenge_method=plain`
の部分がPKCEの設定です。

`code_challenge_method`は`plain`の他に`S256`があります。
plainメソッドは入力を変更しないため、code_verifier と code_challenge の値は同じになります。

*本記事ではplainメソッドで、`"challenge"`という文字列を使っているので、code_verifier と code_challengeはどちらも"challenge"となります。

*S256 メソッドは入力の SHA-256 ハッシュを計算し、そのハッシュ値を Base64-URL エンコードします。

アプリはcode_challenge、code_challenge_methodを認可URLに含めることで、認可サーバーに送信され、認可サーバーは、これらを保管しておきます。

そして、アクセストークンリクエストの際に`code_verifier`を付け加えます。
```
--data-urlencode 'code_verifier=challenge' \
```

認可サーバーは`code_verifier`が正しいかどうかを、保管しておいた`code_challenge`を元に検証し、`code_verifier`が正しければアクセストークンを返却し、間違っていれば何も返しません。

code_verifierは正規のアプリしか知ることはないので、第三者にアクセストークンを横取りされることがなくなる、という仕組みです。

図にまとめると以下の様になります。

![Frame 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173182/24ccf02b-dbff-4427-84c7-723e259dd110.png)


## X APIドキュメント
- [OAuth2.0](https://docs.x.com/resources/fundamentals/authentication/oauth-2-0/user-access-token)
- [投稿](https://docs.x.com/x-api/posts/creation-of-a-post)