📌 この記事について
対象読者: OAuth2.0という名前は聞いたことがあるけど、具体的にどうすればよいかわからない。でも、X API経由で投稿してみたい人
ゴール: OAuth2.0とPKCEの概念を理解して、実装に入る準備ができる
X API OAuth2.0とPKCEの仕組みをラーメン屋の比喩でふんわり解説。認証コードフローからcode_verifierの安全な生成方法、CSRF対策まで初心者にもわかりやすく図解で紹介。実装前の概念理解に最適
はじめに
ども!X APIを活用したアプリ開発をしている龍ちゃんです。最近、新卒エンジニア向けに技術研修の資料を作っていたんですが、やっぱり認証周りってちゃんと整理しないとダメだなと痛感しています。
この記事では、OAuth2.0について特に詳しくない方でも、X APIを実装できるようになるための基礎知識を解説しますね。ちゃんと理解したほうが良い概念ではありますが、まずは「ふんわり理解」から始めましょう。
しっかり勉強したい方は、以下の2冊がおすすめです:
- 雰囲気でOAuth2.0を使っているエンジニアがOAuth2.0を整理して、手を動かしながら学べる本[2023年改訂版]
- OAuth、OAuth認証、OpenID Connectの違いを整理して理解できる本 [2024年改訂版]
弊社のブログであれば合わせて読むなら以下の連載がお勧めです!
- 【連載】世界一わかりみの深いOAuth入門 〜 その1:OAuthってなに? 〜
- 【連載】世界一わかりみの深いOAuth入門 〜 その2:アクセストークンとリフレッシュトークン 〜
- 【連載】世界一わかりみの深いOAuth入門 〜 その3:OAuthを認証に使うことの危険性 〜
- 【連載】世界一わかりみの深いOAuth入門 〜 その4:stateパラメーターによるCSRF対策 〜
こちらの内容を活用して作成したアプリの記録は「AIチャットで話すだけ!X予約投稿を完全自動化するシステム構築術
」で解説しています。
なぜOAuth2.0が必要なの?
X APIを使ってツイートを投稿したり、情報を取得したりするには、「このアプリは信頼できる」「このユーザーが許可している」ことを証明する必要があります。
昔ながらの方法(ID/パスワード直接入力)の問題点
もしID/パスワードをアプリに直接入力する方式だと:
- ❌ アプリがパスワードを保存できてしまう
- ❌ パスワードが漏洩したら全てのサービスで不正利用される
- ❌ アプリに全権限を渡すことになる(投稿だけしたいのに、DM読み取りもできる)
OAuth2.0を使うと
- ✅ パスワードを教えずに権限だけ渡せる
- ✅ 必要な権限だけを限定できる(投稿のみ、読み取りのみなど)
- ✅ いつでも権限を取り消せる
まずは、X APIを使用するために必要な基本的な画面を見てみましょう。

X API OAuth2.0の認証画面 – SIOSTechLabアプリへのアクセス許可を求める画面のスクリーンショット
このような画面を表示させてアカウント連携を行い、トークン(アクセストークン・リフレッシュトークン)を取得します。そのトークンを使用してX APIを実行するわけですね。
🍜 OAuth2.0をラーメン屋で例えると
OAuth2.0の仕組みを理解するために、ラーメン屋で例えてみましょう。
登場人物
- あなた: APIを使いたいユーザー(Resource Owner)
- アプリ: あなたが使うアプリケーション(Client)
- 券売機: X の認証システム(認可サーバー / Authorization Server)
- ラーメン屋: X APIサービス本体(リソースサーバー / Resource Server)
全体の流れ
あなたは人気のラーメン屋(X)でラーメン(APIリソース)を食べたいです。でも、このラーメン屋は直接注文できません。専用アプリ経由でしか注文できないルールなんですね。
ステップ1: アプリで注文開始
「このアプリでラーメン食べたい!」とアプリに伝えます。
ステップ2: 券売機で申し込み(認可リクエスト)
アプリが券売機(認可サーバー)に「このラーメンを注文したいです」と申請書を出します。
ステップ3: 本人確認(ログイン・認可)
券売機が「あなた本人ですか?」と確認します。顔認証(Xへのログイン)で本人確認を済ませますね。
ステップ4: 引換券をもらう(コールバック)
本人確認が完了すると、券売機から**一時的な引換券(認可コード: code)**をもらいます。
ステップ5: 引換券→食券に交換(トークンリクエスト)
引換券を持ってアプリが窓口に行き、**本物の食券(アクセストークン)**に交換します。
ステップ6: ラーメンゲット!(リソースアクセス)
食券を渡してラーメン(APIデータ)を受け取ります。
なぜこんなに面倒なの?
もしID/パスワードを直接アプリに教える方式だと、悪意のあるアプリがパスワードを盗んで好き放題できてしまいますよね。
OAuth2.0なら:
- 「このアプリにラーメン注文の権限だけあげる」という権限の制限ができる
- パスワードをアプリに教えなくていい
- いつでも権限を取り消せる(食券を無効化できる)
必要なエンドポイント
この仕組みを実装するには、最小で2つのエンドポイントが必要です:
1. 認証URL発行エンドポイント
券売機への申請書を出すためのエンドポイントですね。ユーザーをX の認証画面にリダイレクトします。
2. コールバックエンドポイント
券売機から発行された引換券(code)を受け取るエンドポイント。このcodeを使ってアクセストークンを取得します。
OAuth2.0の詳細な流れ(シーケンス図)

OAuth2.0認可コードフローのシーケンス図 – ユーザー、クライアントアプリ、認可サーバー、リソースサーバー間の通信フローを図解
各ステップの詳細
| ステップ | ざっくり理解 | 正確な流れ |
|---|---|---|
| 2. 認可リクエスト | 認可リクエストのURLをください | 認可の開始: クライアントがユーザーを認可サーバー(AS)へ誘導し、「この**権限(Scope)**を借りたい」とASに伝える |
| 3〜4. ログイン・認可 | その用途で使用することに合意します。本人確認は行いました | 権限の付与: ユーザーがAS上でログインし、クライアントアプリが要求した権限(Scope)の付与を許可する |
| 5. コールバック | 本人確認了承しました。きちんと処理が行われていますね | 認可コードの受け渡し: ASがクライアントアプリのコールバックURLに認可コード(code)とstateを渡す。これは一時的で、まだサービスにアクセスするためのキーではない |
| 6〜9. トークンリクエスト | 具体的にサービスにアクセスするためにキーの取得を行いますね | トークンへの交換: クライアントがASに対し、受け取ったcodeを提示して、サービスアクセス用の本物のキーであるアクセストークンとの交換を要求する |
| 10. リソースリクエスト | キーを使ってサービスにアクセスを行います | リソースへのアクセス: 取得したアクセストークンを提示して、リソースサーバー(RS)に保護された情報(リソース)へのアクセスを要求する |
X APIで必要な重要パラメータ
1. Scope(スコープ): 「何ができる権限か」
ラーメンの例で言うと「ラーメンだけ注文できる券」なのか「ラーメン+餃子まで注文できる券」なのか、という権限の範囲ですね。
X APIでよく使うScope
| Scope | 説明 |
|---|---|
tweet.read | ツイートを読む権限 |
tweet.write | ツイートを投稿する権限 |
users.read | ユーザー情報を読む権限 |
follows.read | フォロー情報を読む権限 |
follows.write | フォロー/アンフォローする権限 |
重要: 必要な権限だけを要求するのがセキュリティ上重要です。投稿だけしたいのに全権限を要求すると、ユーザーに警戒されちゃいますよね。
2. redirect_uri(リダイレクトURI): 「引換券を受け取る住所」
認可サーバーが「引換券(code)をどこに送ればいいの?」を指定するのがredirect_uriです。
重要なポイント
- X Developer Portalで事前に登録したURLと完全一致する必要がある
- 末尾の
/まで含めて一致させる - 本番環境:
https://yourdomain.com/callback - 開発環境:
http://localhost:3000/callback(ローカル開発用)
よくあるエラー
❌ Developer Portalに登録: <https://example.com/callback>
❌ コードで指定: <https://example.com/callback/>
→ 末尾のスラッシュが違うのでエラー!
✅ Developer Portalに登録: <https://example.com/callback>
✅ コードで指定: <https://example.com/callback>
→ 完全一致でOK!3. state: 「偽造防止のおまじない」
CSRF攻撃を防ぐためのランダムな文字列です。認可リクエストで送ったstateと、コールバックで返ってきたstateが一致するか確認します。
ラーメンの例で言うと「整理番号」のようなものですね。自分の整理番号と違う引換券が返ってきたら怪しいですよね。
// state生成の例
const state = crypto.randomUUID(); // "550e8400-e29b-41d4-a716-446655440000"
sessionStorage.setItem('oauth_state', state);
// コールバックで確認
const returnedState = new URLSearchParams(window.location.search).get('state');
const savedState = sessionStorage.getItem('oauth_state');
if (returnedState !== savedState) {
throw new Error('State不一致!CSRF攻撃の可能性あり');
}PKCEが必要な理由
さて、ここまでの仕組みでも一応動きますが、実はセキュリティリスクがあります。
🚨 認可コード(code)が盗まれたら?
もし通信途中でcodeが盗聴・盗難された場合、以下のような攻撃が可能になります:

OAuth2.0認可コード横取り攻撃のシーケンス図 – PKCEなしの場合に攻撃者が認可コードを盗聴するリスクを図解
具体的には:
- 正規のクライアントアプリで認可リクエストを送る
- 通信途中で攻撃者がcodeを盗聴
- 攻撃者が盗んだcodeを使ってトークンリクエストを送る
- 認可サーバーは正しいcodeなのでトークンを発行してしまう
- 攻撃者がユーザーのリソースにアクセスできてしまう
ラーメン屋で例えると
PKCEなしの場合(危険!):
- 券売機で引換券だけもらう
- 引換券を持って窓口に行けば食券がもらえる
- → 途中で引換券を盗まれたら、誰でも食券に交換できちゃう!
PKCEありの場合(安全!):
- 券売機で注文する時に「愛言葉」を決める
- 券売機には「愛言葉のヒント」だけ伝える
- 引換券をもらう
- 窓口で引換券と「愛言葉の本物」を見せる
- 店員が「ヒントと愛言葉が一致するか」確認してから食券を渡す
→ 引換券を途中で盗まれても、愛言葉がないと食券には交換できない!
PKCEの仕組み
PKCE(Proof Key for Code Exchange)は、認可リクエストしたクライアントとトークンリクエストを行ってきたクライアントが同一であることを証明するための仕組みです。

主要な登場人物
| 用語 | 説明 | ラーメンの例 |
|---|---|---|
| code_verifier | あなただけが知っている秘密の文字列 | 愛言葉の本物 |
| code_challenge | code_verifierをハッシュ化したもの | 愛言葉のヒント |
PKCEの流れ
- 事前準備: クライアントアプリがcode_verifier(秘密の文字列)を生成
- 認可リクエスト: code_challenge(ハッシュ化したもの)を送信
- コールバック: codeを受け取る
- トークンリクエスト: codeと一緒にcode_verifier(元の文字列)を送信
- 検証: 認可サーバーがcode_verifierをハッシュ化して、最初のcode_challengeと一致するか確認
code_verifierのよくある誤解
危険な実装:固定の愛言葉を使い回す
code_verifierを固定文字列や予測可能な値にしてしまうのは、とても危険な実装です。
// ❌ 危険な例:固定文字列
const code_verifier = "my_secret_verifier_2024";これでは、PKCEの意味がなくなってしまいます。
ラーメン屋で例えると
悪い例:
- 店員:「愛言葉は?」
- あなた:「いつも『ラーメン大好き』って決めてるんだ!」
- 盗聴者:「次回も『ラーメン大好き』って言えば食券もらえるな…」
良い例:
- 店員:「愛言葉は?」
- あなた:「今回は『X7mK9pQ2vR8nL3zA』!」(毎回ランダムに自動生成)
- 盗聴者:「今回の愛言葉聞いたけど、次は全然違うやつ使ってる…予測できん!」
正しい実装:機械的にランダム生成
code_verifierは必ず暗号学的に安全な方法で、毎回ランダムに生成する必要があります。
// ✅ 良い例:毎回異なる、予測不可能な愛言葉を生成
function generateCodeVerifier() {
const array = new Uint8Array(32); // 32バイトのランダムデータ
crypto.getRandomValues(array); // 暗号学的に安全な乱数
return base64UrlEncode(array); // URL安全な文字列に変換
}
const code_verifier = generateCodeVerifier();
// 例: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
なぜ機械的に生成するの?
| 理由 | 説明 |
|---|---|
| 人間は弱い | 人が考える「ランダム」は実はパターンがある(誕生日、好きな言葉など) |
| 十分な長さ | 推奨は43〜128文字。短いと総当たりで破られる |
| 毎回違う | 同じ愛言葉の使い回しを防ぐ |
| 予測不可能 | 暗号学的に安全な乱数生成器(crypto.getRandomValues)を使う |
X APIでの推奨仕様
OAuth2.0/PKCEの仕様(RFC 7636)では:
- 長さ: 43〜128文字
- 文字種:
A-Z,a-z,0-9, ,.,_,~(Base64URL) - 生成方法: 暗号学的に安全な乱数生成器
code_challengeの計算方法
愛言葉(code_verifier)から、ヒント(code_challenge)を作ります。これはSHA-256ハッシュ関数を使って計算しますね。
ポイント
- 一方向性: code_challengeからcode_verifierを逆算することは不可能(ハッシュ関数の一方向性)
- 検証方法: 認可サーバーは「受け取ったcode_verifierをハッシュ化したら、最初のcode_challengeと一致するか」だけ確認できる
🔒 セキュリティまとめ
攻撃者の視点で考える
- 引換券(code)を盗聴した! → でも愛言葉(code_verifier)がわからないから食券に交換できない
- ヒント(code_challenge)は見えてる! → でもハッシュ化されてるから、元の愛言葉は逆算できない
- 前回の愛言葉を盗聴した! → 毎回ランダムに変わるから、次回は使えない
よくある実装ミス
code_verifier編
// ❌ ダメな例1:固定文字列
const code_verifier = "my_app_verifier";
// ❌ ダメな例2:短すぎる
const code_verifier = Math.random().toString(36).substring(7); // 7文字程度
// ❌ ダメな例3:予測可能
const code_verifier = `verifier_${Date.now()}`; // タイムスタンプは予測可能
// ❌ ダメな例4:弱い乱数
const code_verifier = Math.random().toString(36); // Math.random()は暗号学的に安全ではない
// ✅ 良い例:暗号学的に安全なランダム生成
const code_verifier = generateCodeVerifier(); // 前述の関数redirect_uri編
// ❌ ダメな例:末尾のスラッシュが不一致
// Developer Portal登録: https://example.com/callback
const redirect_uri = "https://example.com/callback/"; // 末尾に / あり
// ✅ 良い例:完全一致
const redirect_uri = "https://example.com/callback";
state編
// ❌ ダメな例:stateの検証を忘れる
const code = new URLSearchParams(window.location.search).get('code');
// stateの確認をしていない!
// ✅ 良い例:stateを検証
const returnedState = new URLSearchParams(window.location.search).get('state');
const savedState = sessionStorage.getItem('oauth_state');
if (returnedState !== savedState) {
throw new Error('State不一致!CSRF攻撃の可能性あり');
}X API での具体的な設定
Developer Portalで設定すること
- Appの作成
- https://developer.x.com/en/portal/dashboard にアクセス
- 新しいAppを作成
- OAuth 2.0の有効化
- App設定画面で「User authentication settings」を編集
- OAuth 2.0を有効にする
- Callback URLの登録
- 開発環境(Ngrok必要!):
http://localhost:3000/callback - 本番環境:
https://yourdomain.com/callback - 重要: 末尾のスラッシュまで含めて正確に登録
- 開発環境(Ngrok必要!):
- Permissionsの設定
- Read: ツイート読み取りのみ
- Read and Write: ツイート読み取り+投稿
- Read and Write and Direct Messages: DM含む全権限
まとめ
この記事で学んだこと
- OAuth2.0の必要性: パスワードを教えずに権限だけ渡せる仕組み
- 全体の流れ: ラーメン屋の例で理解する認可フロー
- PKCEの重要性: codeの盗聴・盗難を防ぐ仕組み
- code_verifierの生成: 機械的にランダム生成することの重要性
- 重要パラメータ: scope, redirect_uri, stateの役割
実装時の注意点
- ✅ code_verifierは毎回ランダム生成(43文字以上)
- ✅ 暗号学的に安全な乱数生成器を使用
- ✅ redirect_uriは完全一致させる
- ✅ stateでCSRF対策
- ✅ 必要最小限のscopeを要求
次のステップ
この記事でOAuth2.0とPKCEの概念は理解できました!次は実際に実装してみましょう:
- X Developer Portalでアプリを作成
- 認証URL発行エンドポイントの実装
- コールバックエンドポイントの実装
- トークンを使ってAPI実行
実装編の記事もお楽しみに!
参考資料
推奨書籍
- 雰囲気でOAuth2.0を使っているエンジニアがOAuth2.0を整理して、手を動かしながら学べる本[2023年改訂版]
- OAuth、OAuth認証、OpenID Connectの違いを整理して理解できる本 [2024年改訂版]

