セキュリティ●●●●○

OAuth

OAuth ・ おーおーす

パスワードを渡さずに、範囲と期限を限った権限をアプリへ委譲する標準。

✦ 30秒で試す — 航海アプリに予定を読ませても、パスワードは一度も渡さないこの写しを使って、覗いた人はあなたの予定を読める?予想してみる

概要

OAuth(現行は OAuth 2.0)は、「このアプリに、私の Google カレンダーの読み取りだけを許可する」のように、パスワードを渡さずに限定的な権限を委譲するための標準プロトコルです。あるサービスが持っているあなたのデータへのアクセス権を、別のアプリに「一部だけ・期限付きで」貸し出す仕組み、と言い換えられます。

「Google カレンダーと連携」「GitHub と連携」といったボタンを押すと、画面が一瞬 Google や GitHub に切り替わり、「このアプリに次の権限を許可しますか?」と確認されてから元のアプリに戻ってきます。この往復の裏側で動いているのが OAuth です。許可の証として発行される「アクセストークン」という言葉も、ここから広まりました。

注意したいのは、OAuth はあくまで認可(何をしてよいかの許可)のプロトコルで、認証(誰であるかの確認)の仕組みではないことです。「Google でログイン」のようなソーシャルログインは、OAuth の上に認証の層を載せた OIDC(OpenID Connect)が担っています。OAuth はその土台にあたります。

なぜ生まれたか

OAuth 以前、あるサービスが別サービスのデータを使うには、ユーザーが自分のパスワードを第三者のアプリにそのまま入力するしかありませんでした。たとえば「Gmail の連絡先を読み込む」機能のために、Gmail のパスワードをそのアプリに預けます。するとアプリは、メールの全削除もパスワード変更もできる全権を握ります。ユーザーは後から「連絡先の読み取りだけ」に権限を絞ることも、そのアプリだけを無効にすることもできず、パスワードを変えればすべての連携が一斉に切れてしまいます。アプリ側にパスワードが保存されるので、そこから漏れる危険もありました。

この「全か無か」の委譲を解決するために、2007年に OAuth 1.0 がまとまり(2010年に RFC 5849)、2012年に実装しやすく再設計された OAuth 2.0(RFC 6749)が標準化されました。パスワードの代わりに「範囲(スコープ)と期限を限ったトークン」を発行することで、権限を細かく貸し出し、アプリごとに個別に取り消せるようにしたのです。その後も攻撃の知見を取り込んで改訂が続いており、2025年のセキュリティのベストプラクティス(RFC 9700)では、インプリシットフローの廃止と、すべてのクライアントでの PKCE の使用が推奨されています。

詳細

登場人物は4つ、経路は2本

OAuth 2.0 には4つの役割が登場します。データの持ち主である「リソースオーナー」(=ユーザー)、データを使いたい「クライアント」(=連携アプリ)、同意を確認してトークンを発行する「認可サーバ」(例: Google のアカウント基盤。組織の IdP がこの役を担うことも多い)、トークンと引き換えにデータを返す「リソースサーバ」(例: Google カレンダーの API)です。認可サーバとリソースサーバは同じ事業者が運営することが多いですが、役割としては別物です。

この4者の間には、信頼度の違う2本の経路があります。1本は、ユーザーのブラウザをリダイレクトさせて運ぶ「フロントチャネル」です。ここを流れるものは URL に載るので、ブラウザの履歴やサーバのアクセスログ、Referer ヘッダなどから漏れる可能性があります。もう1本は、クライアントのサーバが認可サーバやリソースサーバと直接 HTTPS で話す「バックチャネル」で、ブラウザを通りません。OAuth の設計は、どちらの経路に何を流すかでほぼ説明できます。

裏口(バックチャネル): サーバ同士が HTTPS で直接話す。ブラウザを通らない認可サーバ同意を確かめ、鍵を発行クライアント連携アプリのサーバリソースサーバユーザーのデータを持つ APIコード+シークレット(または verifier)→ アクセストークンアクセストークン→ 許された範囲のデータ表口(フロントチャネル): ブラウザのリダイレクト。URL に載り、履歴・ログに残るブラウザユーザーが同意する場所表口を流れるものclient_id・scope・redirect_uri・statecode_challenge・認可コード(使い捨ての引換券)覗き窓: 履歴・アクセスログ・Referer
OAuth の4つの役割と2本の経路。表口(ブラウザ経由)には単体では使えないものだけを流し、本物の鍵は裏口で渡す

認可コードフローを順に追う

OAuth の中心にあるのが「認可コードフロー」です。リダイレクトが往復する少し込み入った流れなので、ブラウザを1人の参加者として置いて追うと見通しが良くなります。

リソースサーバ認可サーバクライアントブラウザリソースサーバ認可サーバクライアントブラウザここからはブラウザを経由しないサーバ間通信連携ボタンを押す302 認可エンドポイントへclient_id・scope・redirect_uri・stateログインと同意の画面ログインして許可302 redirect_uri へ・code と state 付きcode と state を届けるcode とクライアントシークレットアクセストークン・リフレッシュトークンBearer トークンを添えて API 呼び出し許可された範囲のデータ

(1)ユーザーがクライアントアプリで「Google と連携」ボタンを押すと、(2)クライアントはブラウザを認可サーバの認可エンドポイントへリダイレクトさせます。この URL には「どのアプリが(client_id)」「どの権限を求めていて(scope)」「許可後にどこへ戻すか(redirect_uri)」に加えて、推測できない乱数 state が含まれます。(3)認可サーバはユーザー本人を認証し(ログイン済みなら省略)、「このアプリにカレンダーの読み取りを許可しますか?」という同意画面を出します。パスワードを入力する相手は認可サーバだけで、クライアントは一度も見ません。(4)ユーザーが許可すると、認可サーバはブラウザを redirect_uri へ戻し、クエリに使い捨ての「認可コード」と、預かった state を添えます。クライアントは、戻ってきた state が自分の送ったものと一致するかを確かめます。ここまでがフロントチャネルです。

(5)認可コードを受け取ったクライアントは、今度はブラウザを経由せず、サーバから直接、認可サーバのトークンエンドポイントへ HTTP リクエストを送ります。認可コードに、自分が正規のアプリであることを示すクライアントシークレット(事前に発行された秘密の値)を添えます。(6)認可サーバは両方を検証し、アクセストークンと、多くの場合リフレッシュトークンを返します。(7)以降クライアントは、リクエストの Authorization ヘッダにアクセストークンを添えて(Bearer 方式)リソースサーバの API を呼び、許可された範囲のデータを受け取ります。

下の部品では、持ち物のチップを自分の手で次の相手へ運んでフローを進めます。ブラウザを通ったものがアドレスバーに載ること、最後に攻撃者の立場で盗んだものを使ってみると何が起きるか、を確かめてください。

⚡ 体験: OAuth の持ち物を自分の手で運ぶ
方式
サーバの世界 — 金の線は HTTPS のサーバ間通信(ブラウザを通らない)ブラウザの世界 — 破線はリダイレクト(URL に載って履歴やログに残る)🔒 cal-app.example/settingsアドレスバー(履歴に残る)認可サーバ鍵を発行するクライアントカレンダーアプリリソースサーバあなたの予定を持つブラウザ(あなた)覗き見る者履歴・ログ・Refererシークレット予定データ「連携する」
手順 1 / 8
あなたが運んだ順に描かれるシーケンス図
ブラウザクライアント認可サーバリソースサーバ覗き見る者まだ何も運んでいません

あなたはカレンダーアプリに Google カレンダーの予定を読ませたい。まずは金の輪のチップをドラッグして、アプリ(クライアント)へ運んでください。タップするとおまかせで運びます。

なぜ二段階に分けるのか

「なぜ認可コードという中間物を挟むのか。最初からトークンを返せばよいのでは」というのが定番の疑問です。答えは上の2本の経路にあります。フロントチャネルはブラウザのリダイレクトを経由するので、URL が履歴やログに残り、漏れる可能性を消しきれません。そこで、露出する経路には「単体では使えない使い捨てのコード」だけを流し、本物のアクセストークンはシークレットで守られたバックチャネルでだけ受け渡します。経路の信頼度に応じて渡すものを分ける、というのがこの設計の核心です。もちろん、すべての経路が HTTPS であることが大前提です。

かつて SPA 向けとされた「インプリシットフロー」は、この逆をしていました。トークンを直接リダイレクトの URL(フラグメント)で返すので、漏れたトークンはそのまま使えてしまいます。アクセストークンは「持参人(Bearer)」型の鍵で、持っていれば誰でも使えるからです。このためインプリシットフローは現在は非推奨です。

PKCE: シークレットを持てないアプリのために

SPA やスマホアプリは、コードがユーザーの手元で動くので、シークレットを埋め込んでも取り出されてしまいます。そこで生まれたのが PKCE(Proof Key for Code Exchange、「ピクシー」)です。クライアントは認可リクエストのたびに使い捨ての乱数 code_verifier を作り、そのハッシュ値 code_challenge だけをフロントチャネルで送ります。コードをトークンに交換するときに、元の code_verifier をバックチャネルで見せます。コードを盗んだ攻撃者は、URL に載ったハッシュから verifier を逆算できないので、交換に失敗します。PKCE はもともと公開クライアント向けでしたが、コードの差し込み攻撃にも効くため、いまはシークレットを持つ機密クライアントも含めた全クライアントで使うことが推奨されています。

そのほかのグラント

ユーザーが介在しない機械どうしの連携では、クライアント自身の資格情報でトークンを得る「クライアントクレデンシャルズ」グラントを使います(バッチ処理やサービスアカウントがこれにあたります)。テレビや CLI のように入力が不便な機器には、別の端末で承認させる「デバイス認可」グラントがあります。ユーザーのパスワードをクライアントが直接受け取る「リソースオーナーパスワードクレデンシャルズ」は、OAuth が解決しようとした問題そのものなので、非推奨です。

トークンの設計と実務の落とし穴

アクセストークンは数分〜1時間程度の短命にし、期限切れ後はリフレッシュトークンで再発行するのが定石です。漏れても被害を時間で区切れます。トークンの実体は、認可サーバ側で管理する不透明な文字列のこともあれば、JWT として自己完結的に検証できる形式のこともあります。前者は失効させやすく、後者はリソースサーバが認可サーバに問い合わせずに検証できる、というトレードオフです。

実務での典型的な落とし穴は次のとおりです。

  • redirect_uri の照合を緩くする: 前方一致やワイルドカードで照合すると、認可コードを攻撃者のサイトへ送らせる手口が成立します。登録時も照合時も完全一致にします。
  • state を省く: 攻撃者が自分の認可コードを被害者のブラウザに踏ませ、被害者のアカウントに攻撃者のデータを連携させる、CSRF 型の攻撃を許します(PKCE でも防げます)。
  • scope を必要以上に広く要求する: 最小権限の原則に反し、漏えい時の被害を広げ、同意画面でユーザーを遠ざけます。
  • 「アクセストークンが取れた=本人確認ができた」と誤解する: ログインが目的なら、ID トークンで「誰が・いつ・どのアプリ向けに認証されたか」を保証する OIDC を使います。

試問

カレンダーアプリ(クライアント)に、Google(認可サーバとリソースサーバ)の予定を読ませる連携です。持ち物のチップを次の相手へ運ぶ(タップでおまかせ)とフローが進み、ブラウザを通ったものはアドレスバーに載って、覗き見る者に写し取られます。連携が終わると、最後は覗き見る者として盗んだ写しを使ってみる番です。上の切り替えで方式を選べます。

✦ 命題挑戦中

シークレットを隠し持てないスマホアプリとして連携を終え、覗き見る者が盗んだ写しでは予定を盗めないようにせよ

  • シークレットを使わない方式を選ぶ
  • 連携を最後まで終え、アプリが予定を受け取る
  • 覗き見る者が盗んだ写しを使っても、拒否される
⚡ 体験: OAuth の持ち物を自分の手で運ぶ
方式
サーバの世界 — 金の線は HTTPS のサーバ間通信(ブラウザを通らない)ブラウザの世界 — 破線はリダイレクト(URL に載って履歴やログに残る)🔒 cal-app.example/settingsアドレスバー(履歴に残る)認可サーバ鍵を発行するクライアントカレンダーアプリリソースサーバあなたの予定を持つブラウザ(あなた)覗き見る者履歴・ログ・Refererシークレット予定データ「連携する」
手順 1 / 8
あなたが運んだ順に描かれるシーケンス図
ブラウザクライアント認可サーバリソースサーバ覗き見る者まだ何も運んでいません

あなたはカレンダーアプリに Google カレンダーの予定を読ませたい。まずは金の輪のチップをドラッグして、アプリ(クライアント)へ運んでください。タップするとおまかせで運びます。