API キーと OAuth は別々の問題を解くもので、選択は一つの問いから始まります。この構図に第三者はいるか。

自社のサーバーが自分の名義で他社の API を呼ぶなら、キーです。他社のアプリがあなたのユーザーの名義であなたの API を呼ぶなら、OAuth です。第三者がいない場面では、OAuth は儀式を増やすだけで安全性は増やしません。

以下では、両者が実際どこで違うのか、OAuth がこの数年で何を変えたのか、そして API キーについてほとんど誰も書かないのに本番運用の付き合い方を決めてしまう性質を扱います。

API キー:「このプログラムは許可されている」と告げる一本の文字列

キーとは、リクエストヘッダーに載せる長いランダム文字列です。

X-Api-Key: lix_live_9f2c...

サーバーはそのキーを見つけ、どのアカウントのものかを判断し、リクエストを通します。仕組みはこれだけです。

そこから導かれる性質は次のとおりです。

  • キーは自分では期限切れにならない。 誰かが失効させるまで生き続けます;
  • キーが示すのはプログラムであって人ではない。 チームの誰が呼び出したのかは普通わかりません;
  • キーはアカウントにできることすべてを許す。 別途権限を絞っていない限りは;
  • キーは唯一の秘密である。 漏れればアクセスが漏れます。途中の段階はありません。

OAuth:同意についての取り決め

OAuth 2.0(RFC 6749)が答えるのは別の問いです。登場人物は四者。データの持ち主、それを欲しがるアプリ、認可サーバー、そしてデータを抱えるサーバーです。

流れはおおよそこうです。アプリは利用者をサービス自身のページへ送ります。利用者は誰が何を求めているかを見て、同意します。アプリは code を受け取り、それをアクセストークンに交換し、そのトークンで API を呼びます。トークンの寿命は短く、数分から数時間。切れたらリフレッシュトークンで新しいものに替えます。

これによって得られるもので、キーには原理的に出せないものがあります。

  • 利用者のパスワードがアプリに渡らない。 意義はここに尽きます;
  • 権限がスコープに分かれる。「リンクを読む」と「リンクを削除する」は別々の許可です;
  • 同意が目に見え、取り消せる。 利用者は接続済みアプリの一覧を見られ、他に触れずに一つだけ切断できます;
  • トークンは勝手に失効する。 盗まれたアクセストークンも一時間後には無価値です。

代償は複雑さです。認可サーバー、アプリの登録、同意画面、リフレッシュトークンの保管と更新、そして呼び出しごとの失効処理が要ります。

判断を左右する軸での比較

API キー OAuth 2.0
何を証明するか プログラム プログラムが代理する利用者
有効期間 無期限 アクセストークンは数分〜数時間
アクセスの止め方 キーを失効させる 同意またはトークンを取り消す
権限の範囲 通常はアカウント全体 動作ごとのスコープ
第三者が持つ秘密 キーそのもの トークンのみ、パスワードは渡らない
導入の手間 ヘッダー一つ 認可サーバーと一連の流れ
向く場面 サーバー間、スクリプト、社内連携 公開アプリ、マーケットプレイス、「◯◯でログイン」

選び方の規則

問うべきは、抽象的にどちらが安全かではなく、誰が誰にアクセスを委ねているかです。

自社サーバー → 他社の API、アカウントは自社のもの
    → API キー

他社アプリ → 自社の API、アカウントは利用者のもの
    → OAuth

定期実行、CI、バックエンド連携
    → API キー

利用者がアプリ単位で許可を見て取り消す必要がある
    → OAuth

ポストバックの受信はキーの教科書的な事例です。あなたのサーバーが我々のサーバーに「注文が決済された」と伝えるとき、そのやり取りに利用者は一人もおらず、同意する人もいなければ、同意画面を置く場所もありません。逆向きの webhook も同じです。

目を離している間に OAuth は変わった

この話題の記事の半分は 2015 年時点の OAuth を説明しています。以来、かなり絞り込まれました。

2025 年 1 月、IETF は RFC 9700, Best Current Practice for OAuth 2.0 Security を公開しました。この文書は実際の攻撃から得た知見を集約し、かつては許容されていた二つの方式を正式に非推奨としました

  • implicit grant — トークンをアドレスバーにそのまま返していたもの;
  • resource owner password credentials — アプリが利用者に ID とパスワードを直接尋ねる方式。OAuth がまさに無くそうとしたものです。

同時に PKCE が、従来のようにモバイルだけでなく、サーバーサイドを含むすべてのクライアント種別で必須になりました。

別途知っておく価値があるのは、OAuth 2.1 が今なお草案であることです。公開済みの RFC ではありません。同じ変更を一つの文書にまとめたもので、PKCE 必須、リダイレクト URI の厳密一致、implicit と password grant の廃止、クエリ文字列へのトークン禁止が入ります。現行標準として引用するのは早すぎます。

実務上の結論は単純です。implicit flow を勧める OAuth の手引きを読んでいるなら、その手引きは古くなっています。

API キーの、あまり語られない弱点

キーは期限切れになりません。つまり失効操作が唯一の手段です。ところがここで厄介なことが出てきます。失効はたいてい即時ではありません。

リクエストごとにキーを検証するとは、その都度データベースを引くということです。まともな流量のある API なら、この結果はキャッシュされます。我々の場合、キーとアカウントの対応はキャッシュに五分間留まります。つまり「失効」を押してから実際にアクセスが止まるまで最大五分あり、その間は漏れたキーが働き続けます。

これは手落ちではなく取引です。キャッシュがなければ全リクエストがデータベースを叩きます。キー方式の API はたいてい似た取引をしていますが、声に出して言われることが少ないだけです。知っておく理由は二つ。第一に、実際に漏洩したときキーの失効は最後の行動ではなく最初の行動で、その後にこの数分間に何が起きたかを確かめる必要があります。第二に、これこそ OAuth の短命トークンが反対側から解いている問題です。あちらはデータベースを介さず自分で切れます。

キーと痛い目を見ずに付き合う

キーで足りるなら——そして大半の連携では足ります——最低限これだけは守ります。

  1. 保存するのはハッシュだけにする。 サーバーは受け取ったキーのハッシュを保存済みのものと突き合わせ、元の文字列は作成時に一度だけ利用者に見せます。我々は SHA-256 で照合しています。
  2. キーに接頭辞を付ける。 lix_live_... の形の文字列は、リポジトリやログ基盤の秘密情報スキャナーが見分けられます。
  3. 連携ごとに別のキーを使う。 一本を失効させても他が巻き添えにならず、記録からどれが漏れたのかも特定できます。
  4. キーをクエリ文字列に入れない。 アドレスは Web サーバーのログ、Referer ヘッダー、ブラウザの履歴に残ります。ヘッダーだけにしてください。
  5. 事故が起きてからではなく、計画的に入れ替える。 一度でも入れ替えたキーは次も速やかに入れ替わります。一度も替えていないキーは、四か所に直書きされていることが後で判明します。
  6. 最終使用日時を見る。 半年触れられていないキーは予備ではなく、開いたままの扉です。

我々が使っているもの

Lix.li の API はキー認証です。X-Api-Key ヘッダー、管理画面での作成と失効、ハッシュによる照合、キーごとの独立したリクエスト制限。OAuth はありませんが、これは意図的です。この API の用途はサーバー側に閉じており——リンクを作る、統計を取る、コンバージョンを受け取る——そのどれにも、利用者が同意を与えるべき第三者が登場しないからです。

もし他社のアプリを顧客のアカウントへ入れる必要が出てくれば、キーでは足りなくなります。利用者は誰に何を許したのかを見られ、他を壊さずに一つのアプリだけ切断できなければなりません。そこで初めて OAuth は儀式であることをやめ、唯一誠実な選択肢になります。

呼び出しそのものは API ドキュメントにまとまっており、無効なキーと失効したキーでどの応答コードが返るかもそこで確認できます。隣接する話題として、301 リダイレクトが POST を GET に変えてしまう理由は、ポストバックの受信を組む人なら読んでおく価値があります。認証付きのリクエストが、送ったときとは違うメソッドで到着することがあるからです。