本記事は元々 blog.logto.io に掲載されたものです。
すべての開発者 CLI は最初のコマンドとして login を備えています。しかし、認証(auth)の仕組みは CLI ごとに異なります。
GitHub はコードを表示し、ブラウザを開いてその認証を求めます。AWS は PKCE ベースのSSO用にブラウザを開きます。Stripe はダッシュボードでペアリングコードの確認が必要です。新しい AI ツール(Claude Code、OpenAI Codex CLI、Cursor)もそれぞれ独自の方式を採用しています。
CLI を開発する際、認証は最初に考えるべき機能のひとつです。認証方式を誤れば、ユーザーの不満やセキュリティ監査につながることもあります。さらに、最近はプログラムから CLI ツールを呼び出す AI コーディングエージェントも登場しており、そのリスクはより高くなっています。いまや人間だけでなく、自律プロセスにも認証情報を渡す場面があるのです。
ここでは、重要な4つの認証方式、それぞれの主要ツールでの実装例、また避けるべき失敗例を解説します。
4 つの方式をひと目で比較
まずはざっくり比較表です。
| 方式 | 最適な用途 | セキュリティ | ブラウザは必要? |
|---|---|---|---|
| OAuth デバイスコードフロー | ヘッドレス環境, SSH | 高い | 不要(同じマシン上の場合) |
| ブラウザベース OAuth(localhost リダイレクト) | ローカル開発 | 最も高い | 必要 |
| API キー / PAT | オートメーション, CI/CD, プロトタイピング | 中程度 | 不要 |
| クライアントクレデンシャル | サービス間通信 | 高い | 不要 |
それぞれ一長一短があります。以下で順に詳しく解説します。
1. OAuth デバイスコードフロー(RFC 8628)
この方式では CLI が ABCD-1234 のようなコードと URL を表示し、任意のデバイスでその URL を開いてコードを入力してもらう仕組みです。
採用例: GitHub CLI(デフォルト)、Azure CLI(--use-device-code)、Vercel CLI(2025年よりデフォルト)、OpenAI Codex CLI(β機能)
動作の流れ
-
cli loginを実行 - CLI が認証サーバーにデバイスコードをリクエスト(
client_idとスコープも送る) - サーバーが3つ返答:
device_code(内部識別子)、user_code(ユーザー入力用短縮コード)、verification_uri(入力サイトのURL) - CLI がコードと URL を表示し、5秒ごとに認証サーバーへポーリングを開始
- 任意のデバイスでその URL を開き、コードを入力して認証(パスワード、SSO、パスキー、MFA 等)
- 承認後、次回ポーリングでアクセストークンとリフレッシュトークンを取得
- CLI がこれらを保存し、認証完了
開発者に好まれる理由
最大の利点:どこでも使えること。リモートサーバーへの SSH セッションでも、Docker 内部でも、ローカルブラウザがない Cloud IDE でも動きます。ブラウザは CLI と同じマシンである必要がありません。
また、企業の認証(SAML、OIDC、MFA)にもフル対応(ブラウザ上で実施)。CLI がパスワード等に触れることはありません。
多くが見逃すセキュリティ課題
デバイスコードフローにはフィッシングリスクがあります。攻撃者は自分用にデバイスコードを発行し、被害者にコード入力させることで攻撃者セッションを正規認証させられます。この攻撃は現実に報告されており、AWS SSO デバイスコード認証でも確認されています。
これを受けて AWS はデフォルトを変更。AWS CLI v2.22.0 から aws sso login の初期フローは PKCE ベースの認証コードフローとなり、デバイスコードフローは --use-device-code 明示時のみ利用可となりました。
一方で、マイクロソフト自身のテナントでは条件付きアクセスでデバイスコードフロー自体を遮断し始めています。これは彼らが高リスクの認証方式と見なしている強いサインです。
こうして面白い分岐が起きています。Vercel は 2025年9月からデフォルトでデバイスコードフローを採用。一方 AWS はこれを避ける形です。本当にブラウザが開けない環境には有用ですが、ローカルでブラウザ利用可能なら PKCE の方が安全です。
また認証プロバイダー側の対応も進んでおり、Logto も v1.38.0 で OAuth 2.0 Device Authorization Grant をリリースしました(OSS & クラウドで利用可能)。RFC 8628 の正しい実装(コード有効期限、レート制限、ポーリング、UX設計など)は手間がかかるため、プロバイダー任せにできるのは大きなメリットです。
RFCより技術的注意点
-
expires_inは認証サーバーが設定(RFC例は1800秒=30分、必須値ではない) - ポーリング
intervalが指定されない場合は 5秒 デフォルト -
slow_downエラー時は 5 秒追加 - デバイスコードは使い捨てで短期間のみ有効にすべき
- すべてのトークン交換は HTTPS 経由必須
2. ブラウザベース OAuth(localhost リダイレクト)
開発者ローカルPC上で最も一般的なパターン。login でブラウザが開き認証、認証後は一時的ローカル HTTP サーバーにリダイレクトされてトークンをやり取りします。最新方式は必ず PKCE(読み「ピクシー」)を重ね、流出耐性を高めています。
採用例: Claude Code, gcloud CLI, Terraform CLI, AWS CLI v2.22+(SSO で PKCEデフォルト)
動作の流れ
-
cli loginを実行 - CLIがランダムなローカルポートに一時 HTTP サーバーを立ち上げ(例:
http://127.0.0.1:8742) - 認証プロバイダーの認可エンドポイントを開く(リダイレクト先にそのローカルURLを指定)
- ブラウザで認証(SSO、パスワード、パスキー等)
- 認証後
http://127.0.0.1:8742/callback?code=XXXX&state=YYYYにブラウザがリダイレクト - ローカルサーバーが認可コードを受け取り、バックチャネルで HTTPS 経由トークン交換
- 「成功しました。タブを閉じてください」ページを表示
- CLIはトークン保存&ローカルサーバー終了
UX は「何も書かずクリックだけで完了」と非常に滑らかです。
失敗するケース
CLI がブラウザを開いたり、localhost にバインドできない場合は不可:
- リモートサーバーへの SSH セッション
- Docker コンテナ内(ポートフォワード難易度アップ)
- CI/CD パイプライン
- ヘッドレスサーバー
- 一部の制限された企業環境
多くのツールがデフォルトでブラウザ OAuth を使いつつ、代替手段としてデバイスコードフローや API キーサポートを提供しています。
よく見かける3つのセキュリティ落とし穴
落とし穴1:127.0.0.1 でなく 0.0.0.0 でバインドする
最もよくある重大ミス。全ネットワークでコールバックサーバーが公開されてしまい、だれでも認可コードを傍受可能。
// 悪い例:ネットワーク上全員がアクセスできる
server.listen(8742, '0.0.0.0');
// 良い例:自ホスト以外からアクセス不可
server.listen(8742, '127.0.0.1');
多くの HTTP サーバーライブラリのデフォルトが 0.0.0.0 であるため、特に注意。
落とし穴2:state パラメータの検証不足
state パラメータは CSRF 対策です。これがないと攻撃者が偽セッションで認証コードを CLI に渡せます。
// 悪い例:CSRF 対策なし
app.get('/callback', (req, res) => {
const code = req.query.code;
exchangeCodeForTokens(code); // 攻撃者からの可能性あり
});
// 良い例:state の検証
const state = crypto.randomBytes(32).toString('hex');
app.get('/callback', (req, res) => {
if (req.query.state !== state) {
return res.status(400).send('Invalid state');
}
exchangeCodeForTokens(req.query.code);
});
落とし穴3:PKCE を使わない
OAuth フローが PKCE(Proof Key for Code Exchange)を使っていないと、認可コードを傍受された場合にリプレイ攻撃が成立します。
PKCE では:
- フロー開始時に CLI がランダムな
code_verifierを生成 - これを SHA-256 でハッシュした
code_challengeを作成 - 認可リクエスト時にチャレンジを送信
- 認可コード交換時にオリジナル
code_verifierを送信 - サーバーがこれらの一致を検証
攻撃者は認可コードだけでは code_verifier を持たないため、トークン交換が不可能です。
これが AWS CLI v2.22+ で PKCE をデフォルト化した理由です。ローカルでブラウザが使えるならブラウザ OAuth + PKCE が最良の UX と最高セキュリティを両立。デバイスコード方式はどうしても同一マシンでブラウザが使えない場合(SSH、コンテナ、リモート開発など)向きです。
3. API キーと個人用アクセストークン
最も単純明快な方式。Web ダッシュボードでトークンを発行し、CLI 設定や環境変数に貼り付けて準備完了。
採用例: Stripe CLI(ログインオプション)、npm、pip、多くの AI コーディングツール(Claude Code= ANTHROPIC_API_KEY、OpenAI = OPENAI_API_KEY など)、Aider
動作の流れ
- サービスのWebダッシュボードにログイン
- 設定→APIキー(または個人用アクセストークン等)
- 新規キーを発行(通常ランダム長&プリフィックス e.g.,
sk_live_,ghp_,npm_) - 設定ファイル(例:
~/.config/stripe/config.toml、~/.aws/credentials)または環境変数へ保存
CLI は起動時にこれを読み取り、APIリクエスト時の Authorization ヘッダーに Bearer トークンとして追加します。
リスクを承知で人気を保つ理由
自動化用途なら API キーは極めて便利。CI/CD やコンテナ、スクリプト、cron, あらゆる環境で環境変数から読み込めます。ブラウザ不要。インタラクティブなプロンプト不要。トークン更新不要。
特に AI エージェント用途では API キーが最短統合路。「Claude Code」や「Cursor」などが API を叩くとき、環境変数にAPIキーがあれば一発です。
具体的なリスク
- 漏洩。 API キーが git コミットやログ、エラーメッセージ、CI 出力に流出。GitHub は毎年100万件超のトークン流出をレポートしています。
- 権限過大。 大抵の API キーは範囲が広く、漏洩時の被害も甚大。
- MFA 無力化。 API キーは多要素認証(MFA)をバイパス。
- ローテーションが困難。 キーを更新するたびに保存先すべて更新が必要。チーム内で連携が大変。
近代的強化策:一時トークン発行パターン
最善策は「APIキー→短命な一時トークンに交換」して使うこと。
AWS の STS(Security Token Service)方式が代表例。長期クレデンシャルは一時クレデンシャル発行時のみ使用、1時間で失効。「aws-vault」などツールで自動化できます。
import boto3
sts = boto3.client('sts')
temp_creds = sts.assume_role(
RoleArn='arn:aws:iam::123456789:role/CLIRole',
RoleSessionName='cli-session'
)
# temp_creds の有効期限は通常1時間
API キー運用でもこのパターン追加を検討しましょう。漏洩リスクが「気づくまで有効」→「1時間だけ」に大幅減します。
4. クライアントクレデンシャルフロー
OAuth 2.0 のサービス間認証用フロー。人間不要、完全に自動化。
利用例: CI/CD パイプライン、バックグラウンドサービス、自動ツール
動作の流れ
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=my_service
&client_secret=secret_value
&scope=api:read api:write
サービス自身の client_id と client_secret を認証サーバーへ送信し、短命アクセス・トークンを取得します。ブラウザやユーザー操作、リダイレクトは不要。
使うべき場面
- サービスやボットが認証必要(人ではない)
- CI/CD パイプラインで利用
- 自動・非インタラクティブな用途
- 「ユーザー」がアプリ自身であるとき
人間認証用途では利用しないでください。MFAやSSO等をサポートしません。
実際の CLI の選択例
公式ドキュメント・ソースコードに基づく正確な一覧。
| CLI ツール | デフォルト方式 | フォールバック | トークン保存先 |
|---|---|---|---|
GitHub CLI (gh) |
デバイスコードフロー(ブラウザ) | PAT(--with-token)、環境変数(GH_TOKEN) |
OS キーチェーン(なければ平文ファイル) |
| AWS CLI v2 | PKCE 認証コードフロー(SSO) | デバイスコード(--use-device-code)、クレデンシャルファイル |
~/.aws/sso/cache/ |
Azure CLI (az) |
WAM(Windows)、ブラウザ認証コード(Linux/macOS) | デバイスコード(--use-device-code) |
~/.azure/msal_token_cache.* |
| Vercel CLI | デバイスコードフロー (新デフォルト, 2025年9月) | API トークン(--token, 環境変数) |
~/.local/share/com.vercel.cli/auth.json |
| Stripe CLI | ブラウザベースペアリングフロー | API キー(--interactive, --api-key 或いは環境変数) |
~/.config/stripe/config.toml |
| gcloud CLI | ブラウザ OAuth |
--no-browser 手動フロー |
~/.config/gcloud/ |
| Claude Code | ブラウザ OAuth | API キー(環境変数, apiKeyHelper) |
OS キーチェーン / ~/.claude/.credentials.json
|
| OpenAI Codex CLI | ブラウザ OAuth | デバイスコード(β)、API キー |
~/.codex/auth.json / OS キーリング |
| Terraform CLI | ブラウザ OAuth | トークン貼り付け | ~/.terraform.d/credentials.tfrc.json |
トレンドは明らかです:ローカル開発ではブラウザベース OAuth がデフォルト、ヘッドレス環境ではデバイスコードをフォールバック、CI/CD や自動化には API キーが採用。 PKCE が最も安全なオプションとして普及中です。
トークン保存:安全な方法、避けるべき方法
正しい認証設計も、保存がズサンならすべて台無しです。
ベスト:OS キーチェーン
各OSには暗号化クレデンシャルストア:
- macOS: Keychain(例:GitHub CLI, Claude Code)
- Windows: Credential Manager
- Linux: Secret Service API(GNOME Keyring, KDE Wallet)
OS側で暗号化・アクセス制御・ハードウェア連携してくれるので、CLI が暗号処理を持つ必要なし。
// OS横断 keytar ライブラリ利用例
const keytar = require('keytar');
// 保存
await keytar.setPassword('my-cli', 'default', token);
// 取得
const token = await keytar.getPassword('my-cli', 'default');
次善策:暗号化設定ファイル
キーチェーン未対応環境(コンテナ、最小 Linux 等)では、権限制限付きファイル保存推奨:
# 所有者のみ読み書き可に設定
chmod 600 ~/.config/my-cli/credentials
絶対に避けるべき
平文トークンファイル。 一見当たり前ですが、まだ多数のツールが実際に使っています。ユーザー権限の全プロセス・バックアップツールが読み取れます。
// 絶対NG
fs.writeFileSync(path.join(homedir, '.myapp/token'), token);
長期保存目的の環境変数。 環境変数はプロセス一覧で見えたり、クラッシュレポーターにログ出力されたりします。CI/CDでは(CI側が秘匿管理するため)OKですが、ローカル開発ではリスク大。
ブラウザ localStorage。 CLI に Web コンポーネントがある場合、トークンを localStorage に保存しないこと。XSS脆弱性=トークン流出となります。
トークンライフサイクル管理
アクセストークン
短命運用を推奨(1時間が標準)。期限切れ時は CLI が自動更新すべきで、日常利用で再ログインは不要です。
リフレッシュトークン
長寿命の更新用クレデンシャル。新しいアクセス・トークン発行時に利用。攻撃価値が高いため最重要ターゲットでもあります(通常は数日〜数ヶ月有効)。
リフレッシュトークンのローテーション
最新の認証システムはリフレッシュトークンを毎回ローテーションします:
- CLI がリフレッシュトークンで新規アクセストークン請求
- サーバーは新アクセストークン と新リフレッシュトークン を返却
- 古いリフレッシュトークンは直ちに無効化
- CLI が両新トークンを保存
これにより盗難時の影響を封じ込め。万が一盗まれたリフレッシュトークンが流用されてもサーバー側が検知し、トークンファミリー全体を無効化します。被害者と不正利用者両方で再認証が必要となり、攻撃者の持続利用を阻止。
よくある実装ミス(コード付)
1. コールバックサーバーを全インターフェースで公開
前述と重複ですが再強調:127.0.0.1 固定、絶対に 0.0.0.0 は使わない。
2. トークンのログ出力
想像以上によく発生しています。デバッグログやリクエストヘッダーのダンプ、サポート用の verbose モード等で表に出ます。
// これが全社のログやサポートチケット、Slack スクショに残ります
logger.debug('Request headers:', headers);
// 改善例:機密ヘッダーを削除
const safe = { ...headers };
delete safe.authorization;
logger.debug('Request headers:', safe);
3. コンテナイメージ内にクレデンシャルを埋め込む
Docker イメージは秘密の保管場所ではない。全レイヤーが抽出可能。
# だれでも pull すればキーを開示できる
ENV API_KEY=sk_live_abc123
# 正しくは:起動時に渡す
ENV API_KEY=""
# docker run -e API_KEY=${API_KEY} my-image
4. トークン有効期限切れ時の優雅な処理不足
途中で期限切れ→例外&クラッシュではなく、自動リフレッシュ or 再認証プロンプトにすべき。
def api_call(endpoint, data):
response = requests.post(endpoint, json=data, headers=auth_headers())
if response.status_code == 401:
if refresh_tokens():
# 新トークンでリトライ
response = requests.post(endpoint, json=data, headers=auth_headers())
else:
raise AuthExpiredError("セッションの有効期限が切れました。再度 `cli login` を実行してください。")
return response
5. 権限最小化原則の無視
本当に必要な範囲(例:repo:read)だけをリクエストしましょう。admin:* 等の広すぎる権限要求は厳禁。AI エージェントが CLI を使う場合、その原則はさらに重要です。
AI エージェント時代の課題
2026年のいま、CLI は人間のコマンド入力専用ではありません。Claude Code や Codex、Cursor の agent mode など、AI コーディングエージェントがプログラムから CLI ツールを叩いています。このことで新たな認証課題が生まれています:
権限委譲。 Claude Code が gh pr create を呼ぶとき、それは「あなたの」GitHub 認証情報を使います。しかし AI エージェントがあなたと同じ権限でよいのでしょうか?最小権限原則上 NG ですが、多くのツールはまだエージェント専用の権限分離機能を持ちません。
クレデンシャル漏洩リスク。 API キーが環境変数にある場合、全サブプロセスが参照可能で AI エージェントの子プロセスからも読めます。Claude Code など一部ツールは短命トークンを生成する apiKeyHelper スクリプトで改善済みですが、普及には至っていません。
エージェントのヘッドレス認証。 サンドボックス環境で動作する AI エージェントはブラウザを開けません。この場合デバイスコードフローが便利ですが、API キーは一切のユーザー操作不要のため、今はそちらが主流です。
監査ログと責任の所在。 AI エージェントに「あなた」名義の認証情報を渡すと、API の監査ログも「人間」と「エージェント」を区別できなくなります。現時点でこの識別方法は標準化されていません。
今後も進化必至の分野ですが、今できるベストプラクティス:
- エージェント用途は権限を最小化したトークンを(scope指定+短命運用)
- 長寿命 API キーではなく短命トークンを利用
- エージェント用クレデンシャルを個人用と分離保管
- API 利用状況をモニタして異常検知
選定フレームワーク
認証方式を選ぶときの簡易指針:
「ユーザーはローカルPCの開発者」
→ PKCE付ブラウザOAuth(最良セキュリティ+スムーズUX)
「CLI が SSH 環境やコンテナでも動作する必要あり」
→ ブラウザOAuthを基本、フォールバックにデバイスコード
「CI/CDなど人手無し自動実行」
→ クライアントクレデンシャル or 権限制限APIキー
「最速実装だけが目的」
→ API キー(後で必ずトークンローテーション追加を検討!)
「企業利用でSSOやMFAが必須」
→ 任意OAuth方式(デバイスコード or ブラウザどちらもOK)、全エンタープライズ認証に対応
「AIエージェントにもCLI利用を想定」
→ APIキー(エージェント統合目的)+ブラウザOAuth(人間用)の両対応、scope分離+短命トークンを組み合わせ
FAQ
デバイスコードフローは安全?
ほとんどの攻撃には十分安全ですが、フィッシング脆弱性があります。攻撃者がデバイスコードを発行してユーザーに認証させればセッション乗っ取りが可能。AWS が SSO のデフォルトを PKCE に移行した一因です。PKCE が現実的に使えないヘッドレス環境ではなお有力ですが、フィッシングリスクには留意。
トークンを環境変数保存してもいい?
CI/CD用途:OK(CI側が暗号化管理&ランタイム注入のため)。ローカル開発用途:NG。OSキーチェーン推奨。環境変数はプロセス一覧やログでどうしても漏洩リスクが高い。
API キーと個人用アクセストークンの違いは?
ほとんど機能差なし。両者とも長寿命API認証情報。多くはプロジェクト/アプリ単位=APIキー、ユーザー紐付け型=個人用アクセストークンという分類。多くのサービスでは実質同義に扱われます。
クレデンシャルはどれくらいの頻度で更新すべき?
アクセストークン:1時間以内ごとの自動更新。
リフレッシュトークン:毎回ローテーション(サーバー側で対応)。
APIキー:最低でも90日ごと、あるいは流出疑い即座更新。
実際にはインシデント後にしかAPIキーをローテーションしない組織も多いですが、本来は事前対応がベストです。
Docker コンテナ内の認証はどうする?
優先順位順で:
- デバイスコードフロー(インタラクティブ時。ブラウザは他マシンOKなので動作)
-
環境変数渡し(
docker run -e API_KEY=${API_KEY}でCI/CD用) -
ボリュームマウントでの認証情報受け渡し(
docker run -v ~/.config/tool:/root/.config/tool:ro)(ローカル開発用)
絶対にイメージ内へのクレデンシャル埋め込みは避けてください。
MCP(Model Context Protocol)認証対応は?
MCPはAIエージェントが外部ツールやサービスに接続するための新標準です。ここでも新しい認証課題が生まれます:エージェント自身の MCP サーバー接続認証情報、さらにそのMCPから各種APIへの認証…。標準化は発展途上。現状ほぼAPIキーやOAuthトークンを MCP 設定ファイル経由で連携しています。今後急速に進化する分野です。
CLI認証は急速に進化中。2年前のベストプラクティス(デバイスコードデフォルトや平文ファイル保存)はすでに陳腐化しています。これから認証実装するなら人間向けはブラウザOAuth+PKCE、自動化にはAPIキー、いずれはAIエージェントが主要ユーザーになる日を見据えて設計してください。