CyberFix Note
セキュアコーディング

OAuth 2.0とOpenID Connectの基礎。認可と認証の違いを仕組みから整理する

対象の目安: Web/アプリを実装する開発者・情報システム担当 / 実務レベル

ソウ攻撃・脆弱性リサーチ担当
・ 約19分で読めます
OAuth 2.0とOpenID Connectの基礎。認可と認証の違いを仕組みから整理する

「Googleで続ける」「GitHubでログイン」といったボタンを押すと、パスワードを新しいサービスに直接渡すことなく、既存のアカウントで別のサービスにサインインできます。この裏側で動いているのが、OAuth 2.0とOpenID Connect(OIDC)という二つの標準です。名前がよく一緒に出てくるうえに、どちらもログインまわりで使われるため、同じものだと受け取られがちですが、担う役割ははっきり分かれています。

ごく短く言えば、OAuth 2.0はアクセスの委譲、つまり「あるアプリに、利用者の資源への限定的なアクセスを許す」ための認可の枠組みです。一方のOpenID Connectは、そのOAuth 2.0の上に「利用者が誰であるかを確かめる」認証の層を足したものです。OAuthが渡すのは何をしてよいかという権限であり、OIDCが伝えるのは誰がログインしたかという本人確認の結果です。この違いを取り違えると、設計が根本からずれます。

この記事では、Web/アプリを実装する方に向けて、OAuth 2.0とOpenID Connectの基礎を一次仕様に沿って整理します。登場人物、認可コードフローとPKCE、アクセストークンとリフレッシュトークンとIDトークンの違い、スコープ、なぜImplicitフローが非推奨になったか、そしてよく足をすくわれる落とし穴までを順に説明します。

OAuthに登場する4つの役割

OAuth 2.0を理解する出発点は、やり取りに関わる役割を分けて捉えることです。RFC 6749は、次の4つの役割を定義しています。写真印刷サービスに、自分のクラウドストレージの写真フォルダだけを読む権限を渡す、という場面を思い浮かべると分かりやすくなります。

役割英語名何をするか
リソースオーナーResource Owner資源の持ち主で、アクセスを許すかどうかを決める利用者本人
クライアントClient資源にアクセスしたいアプリ写真印刷サービス
認可サーバーAuthorization Server本人確認と同意を経てトークンを発行するクラウド側のログイン基盤
リソースサーバーResource Serverトークンを確かめて資源を返すAPI写真を保管するAPI

リソースオーナーは資源の持ち主である利用者、クライアントはその資源を使いたいアプリです。認可サーバーは利用者を確かめて同意を取り、権限を表すトークンを発行します。リソースサーバーは、そのトークンを受け取って中身を確かめ、範囲内であれば資源を返すAPIです。認可サーバーとリソースサーバーは同じ事業者が運用することも、別々に分かれることもあります。

RFC 6749「The OAuth 2.0 Authorization Framework」は、サードパーティのアプリケーションがHTTPサービスへの限定的なアクセスを取得できるようにする認可の枠組みを定義した一次仕様です。リソースオーナー、クライアント、認可サーバー、リソースサーバーという役割と、認可グラント、アクセストークンといった基本概念を規定しています。

ここで大切なのは、OAuthが解決しようとしているのが「クライアントに利用者のパスワードを渡さずに、限定的なアクセスを委ねる」という問題だという点です。写真印刷サービスにクラウドストレージのパスワードそのものを教えてしまえば、写真フォルダ以外も含めて何でもできてしまいます。OAuthは、必要な範囲だけを表すトークンを間に挟むことで、この委譲を安全にします。

OAuthは認可、OpenID Connectは認証

もっとも混同されやすい点なので、独立した節として整理します。OAuth 2.0とOpenID Connectは、担当する判断が異なります。

OAuth 2.0は、正式名称が「The OAuth 2.0 Authorization Framework」で、認可(Authorization)の枠組みです。ここで扱うのは「このクライアントに、この資源へのどのような操作を許すか」という権限の委譲です。OAuth 2.0そのものは、利用者が誰であるかを標準化された形でクライアントに伝える仕組みを主目的にしていません。認可サーバーの中では本人確認が行われますが、その結果を受け取って本人を特定する標準的な手段は、OAuth 2.0だけでは定義されていません。

OpenID Connectは、このOAuth 2.0の上に載せた認証(Authentication)の層です。OAuth 2.0のやり取りを土台にしつつ、認証の結果を表すIDトークンや、利用者の属性を返すUserInfoエンドポイントなどを足すことで、クライアントが標準化された形で「確かにこの利用者がログインした」と判断できるようにします。OpenID Connect Core 1.0の仕様は、OIDCを「OAuth 2.0の上に構築されたシンプルなアイデンティティ層」と位置づけています。

OpenID Connect Core 1.0は、OAuth 2.0プロトコルの上に構築されたアイデンティティ層であり、認可サーバーが実施した認証に基づいてクライアントが利用者の同一性を検証できるようにすると定めています。認証の結果はIDトークンとして表現され、IDトークンはJWTの形式で認証イベントに関するクレームを含むと規定されています。

よくある「OAuthでログインする」という言い方は、日常会話では通じますが、厳密には正確ではありません。ログイン、つまり本人確認の結果を標準的に受け取る部分を担っているのはOpenID Connectであり、OAuth 2.0が担うのはその手前の認可です。認証と認可の違いそのものは、関連記事で基礎から整理しています。

あわせて読みたい

認証と認可の違い。Authentication と Authorization を基礎から整理する

認可コードフローとPKCE

OAuth 2.0にはトークンを受け取るいくつかの方式(グラント)がありますが、現在の中心は認可コードフロー(Authorization Code Grant)です。名前のとおり、いきなりトークンを渡すのではなく、まず一時的な引換券にあたる認可コードを受け取り、それをトークンと交換する二段構えになっています。

流れを追います。クライアントは利用者のブラウザを認可サーバーへ誘導し、認可サーバーは利用者を認証したうえで「このクライアントに、この範囲のアクセスを許してよいか」という同意画面を見せます。利用者が同意すると、認可サーバーはあらかじめ登録されたリダイレクトURIへブラウザを戻し、そこに認可コードを添えます。クライアントは受け取った認可コードを、今度はブラウザを経由しない裏側の通信で認可サーバーの別のエンドポイントに送り、アクセストークンと交換します。

認可コードフロー(PKCEあり)の流れ

  1. 1

    クライアントがcode_verifierを生成し、そのハッシュであるcode_challengeを付けて、利用者のブラウザを認可サーバーの認可エンドポイントへ誘導する

  2. 2

    認可サーバーが利用者を認証し、要求された範囲(スコープ)への同意を求める

  3. 3

    同意を得た認可サーバーが、登録済みのリダイレクトURIへ認可コードを付けてブラウザを戻す

  4. 4

    クライアントが認可コードとcode_verifierをトークンエンドポイントへ送り、正しさが確認されるとアクセストークンなどを受け取る

  5. 5

    クライアントがアクセストークンを付けてリソースサーバーのAPIを呼び、リソースサーバーがトークンを検証して資源を返す

認可コードをそのまま使う方式には、コードが途中で盗まれると悪用されるという弱点があります。特にスマホアプリや、ブラウザ上で完結するシングルページアプリのように、秘密の鍵(クライアントシークレット)を安全に隠しておけない公開クライアントでは、リダイレクトの経路で認可コードを横取りされる恐れが現実的です。これを塞ぐのがPKCE(Proof Key for Code Exchange、ピクシーと読みます)です。

PKCEでは、クライアントが毎回ランダムな秘密の文字列(code_verifier)を作り、そのハッシュ値(code_challenge)を最初の認可要求に付けます。そして認可コードをトークンと交換する段階で、元のcode_verifierを提示します。認可サーバーは、最初に受け取ったハッシュと、後から提示されたcode_verifierが対応するかを確かめます。仮に認可コードだけを盗んでも、対になるcode_verifierを知らなければトークンと交換できないため、コードの横取りが成立しなくなります。

RFC 7636「Proof Key for Code Exchange by OAuth Public Clients」は、公開クライアントに対する認可コード横取り攻撃を緩和する仕組みとしてPKCEを定義しています。クライアントが秘密のcode_verifierを生成し、その変換値であるcode_challengeを認可要求に付け、コード交換時に元のcode_verifierを提示することで、認可コードを盗んだだけの攻撃者がトークンを得られないようにします。

PKCEはもともと公開クライアント向けに考案されましたが、その後の運用経験を踏まえ、OAuth 2.0のセキュリティに関する現行のベストプラクティスであるRFC 9700は、クライアントシークレットを持つ機密クライアントも含めて、認可コードフローではPKCEを使うことを推奨しています。

アクセストークンとリフレッシュトークンとIDトークン

OAuthとOIDCのやり取りでは、性格の異なる複数のトークンが登場します。役割を取り違えると設計を誤るため、代表的な三つを分けて押さえます。

種類由来主な役割主な受け取り手
アクセストークンOAuth 2.0APIを呼ぶための権限を表す(認可)リソースサーバー(API)
リフレッシュトークンOAuth 2.0期限切れのアクセストークンを再取得する認可サーバー
IDトークンOpenID Connect本人確認の結果を伝える(認証)クライアント(アプリ)

アクセストークンは、APIを呼び出すときに「この要求を許可してよい」という認可の情報を運ぶトークンです。リソースサーバーはこれを検証し、範囲内であれば資源を返します。アクセストークンは漏れたときの被害を抑えるために有効期間を短く保つのが一般的です。

リフレッシュトークンは、寿命の短いアクセストークンが切れたときに、利用者へ再ログインを求めずに新しいアクセストークンを受け取るための、寿命の長いトークンです。リフレッシュトークンはアクセストークンより厳重に扱い、認可サーバー以外へは送りません。使うたびに置き換えるローテーションや、使い回しの検知といった対策を重ねます。

IDトークンは、OpenID Connectで発行される、認証の結果を表すトークンです。JWTの形式で、認証イベントに関するクレームを含みます。だれが認証されたかはsubが示し、いつ認証されたか(auth_time)や、どのように認証されたか(acramr)は、必要に応じて含められる任意のクレームです。受け取ったクライアントは、署名に加えて発行者(iss)や宛先(aud)や有効期限(exp)などを検証したうえで、本人がログインしたと判断します。注意したいのは、IDトークンはAPIへのアクセス権を表すものではないという点です。IDトークンを検証せずに信用したり、APIの認可判断にIDトークンを流用したりするのは設計上の誤りです。IDトークンの中身はJWTなので、JWTの署名検証と失効の考え方は関連記事が参考になります。

あわせて読みたい

JWTの仕組みと実装の落とし穴。署名検証とアルゴリズム混同を基礎から

スコープで範囲を絞る

OAuthが「限定的なアクセス」を実現できるのは、スコープ(scope)という仕組みがあるからです。スコープは、クライアントが要求するアクセスの範囲を表す文字列で、認可要求に添えて送ります。たとえば「連絡先を読む」「カレンダーの予定を書く」といった単位で、どこまで許すかを表現します。

利用者は同意画面で、クライアントがどのスコープを求めているかを確認したうえで許可します。認可サーバーは、同意された範囲を反映したアクセストークンを発行し、リソースサーバーはそのスコープの範囲でのみ資源を返します。写真印刷サービスの例で言えば、「写真フォルダを読む」スコープだけを渡せば、そのサービスはファイルの削除や他のフォルダの閲覧はできません。

設計の基本方針は、必要な範囲だけを求めるスコープ最小化です。あれば便利かもしれないという理由で広いスコープを要求すると、クライアントが侵害されたときに委ねた権限がそのまま被害の範囲になります。利用者にとっても、過大な権限を求めるクライアントは同意しづらくなります。要求するスコープは、機能に本当に必要なものへ絞ってください。

なぜImplicitフローが非推奨になったか

かつては、シングルページアプリ向けの簡便な方式としてImplicitフロー(Implicit Grant)が使われていました。これは認可コードを介さず、認可サーバーからブラウザへアクセストークンを直接返す方式で、リダイレクト先のURLのフラグメント部分にトークンが載って戻ってきます。

この直接返す設計そのものが弱点になります。認可応答でアクセストークンをそのまま返すため、ブラウザの履歴やページ内のスクリプト、リダイレクト先の実装や各種のログといった経路に露出しやすく、そこから漏れたりリプレイされたりする恐れがあります。加えて、Implicitフローには認可コードフローのPKCEにあたる横取り対策がなく、トークンの正当な宛先を確かめる手立ても弱いという問題があります。こうした事情から、OAuth 2.0のセキュリティに関する現行のベストプラクティスは、Implicitフローを新規に使わないよう求めています。

RFC 9700「Best Current Practice for OAuth 2.0 Security」は、これまでの運用経験と新たな脅威を踏まえてRFC 6749などのセキュリティ指針を更新し、より安全性の低い方式を非推奨としています。アクセストークンを認可応答で直接返すImplicitグラントは使うべきでないとされ、代わりに認可コードフローとPKCEの組み合わせが推奨されています。

現在の指針は明快で、Webでもモバイルでも、認可コードフローにPKCEを組み合わせる形が基本です。ブラウザ上で完結するアプリについては、トークンをブラウザに露出させない構成として、サーバー側でトークンを扱うBFF(Backend for Frontend)パターンなども整理されています。ブラウザ環境ではXSSでスクリプトが動くとトークンを盗まれる余地があるため、トークンをブラウザから遠ざける設計が重視されています。

IETFの「OAuth 2.0 for Browser-Based Applications」は、ブラウザ上で動くアプリに向けたOAuth 2.0の利用指針をまとめた文書です。認可コードフローにPKCEを組み合わせることを基本とし、トークンをブラウザに保持させずサーバー側で扱うBFFのような構成を、脅威に応じた選択肢として示しています。執筆時点ではIETFのドラフトとして議論が続いています。

よくある落とし穴

フローを正しく選んでも、細部の実装で穴が空くことがあります。とくに繰り返し問題になる点を挙げます。

stateによるCSRF対策

認可要求にはstateというパラメータを添えられます。これは、クライアントが要求時に生成した値を認可応答でそのまま返してもらい、突き合わせることで、その応答が本当に自分が始めた要求への返信かを確かめるための仕組みです。stateを確かめないと、攻撃者が用意した認可応答を利用者に処理させ、攻撃者のアカウントへ被害者を紐づけるといったCSRF(クロスサイトリクエストフォージェリ)が成立し得ます。RFC 6749は、CSRF対策として推測困難なstateの利用を求めています。なお現行のセキュリティ指針では、PKCEを使う場合はPKCE自体がCSRFへの防御も兼ねるとされていますが、いずれにせよ要求と応答の結び付きを確かめる仕組みを欠かさないことが要点です。

リダイレクトURIの厳密一致

認可コードやトークンが戻る先であるリダイレクトURIは、攻撃者にとって格好の狙いどころです。ここに緩い一致(部分一致やワイルドカード)を許すと、攻撃者が細工したURIへ認可コードを送らせ、横取りにつなげられます。対策は、認可サーバー側で、あらかじめ登録されたリダイレクトURIと要求されたリダイレクトURIを、単純な文字列として完全一致で照合することです。現行のセキュリティ指針も、リダイレクトURIの厳密な一致比較を推奨しています。例外として、同指針はネイティブアプリのlocalhostリダイレクトについてはポート番号の違いを許容していますが、原則は事前登録との文字列完全一致です。

注意

リダイレクトURIの照合を「登録済みドメインで始まっていればよい」といった前方一致で済ませると、攻撃者が用意したパスやサブドメインへ認可コードを送らせる余地が生まれます。照合は、登録済みの値との完全一致で行ってください。

トークンの保管

受け取ったトークンをどこに置くかも設計の一部です。ブラウザで動くアプリの場合、localStorageはJavaScriptから読めるため、XSSでスクリプトが動くとトークンを丸ごと盗まれます。前述のBFFのように、トークンをブラウザに露出させずサーバー側で扱う構成や、リフレッシュトークンをHttpOnlyかつSecureのCookieに置く構成が検討されます。モバイルアプリでは、OSが提供する安全な保管領域を使い、平文のファイルや共有領域に置かないことが基本です。

スコープ最小化

前述のとおり、要求するスコープは機能に必要な範囲へ絞ります。広いスコープを既定で求めると、クライアントの侵害時に委ねた権限がそのまま被害範囲になります。新しい機能を足すたびに、本当にそのスコープが要るのかを見直してください。

これらの落とし穴に共通するのは、攻撃者が触れられる経路(URLやリダイレクト先、ブラウザ上の保管場所)を信頼しすぎない、という姿勢です。OAuthとOIDCはAPIの認可とも密接に関わるため、API全体の設計とあわせて考えると理解が深まります。

あわせて読みたい

APIセキュリティの基本。認証認可・レート制限・入力検証とOWASP API Top 10で守る

実装で押さえる手順

ここまでの内容を、実装時に踏む手順としてまとめます。

OAuth/OIDCを安全に組み込む手順

  1. 1

    認可コードフローを選び、公開・機密を問わずPKCEを組み合わせる。Implicitフローは使わない

  2. 2

    認可要求にstateを付け、応答で突き合わせてCSRFを防ぐ

  3. 3

    リダイレクトURIを事前登録し、認可サーバー側で完全一致の照合を確認する

  4. 4

    要求するスコープを機能に必要な範囲へ絞る(スコープ最小化)

  5. 5

    IDトークンは署名・iss・aud・expなどを検証してから本人確認に使い、APIの認可にはアクセストークンを使う

  6. 6

    アクセストークンは短命にし、リフレッシュトークンは厳重に保管してローテーションを設計する

このうち見落とされやすいのが、IDトークンとアクセストークンの使い分けです。IDトークンは本人確認の結果であり、検証したうえでクライアントが利用者を特定する用途に留めます。APIを呼ぶ権限はアクセストークンが担い、リソースサーバーはアクセストークンを検証して認可を判断します。この役割分担を崩さないことが、堅牢な設計の土台になります。

まとめ

OAuth/OIDC実装のチェックリスト

  • OAuth 2.0は認可、OpenID Connectはその上の認証という役割の違いを理解している
  • 認可コードフローにPKCEを組み合わせ、Implicitフローを使っていない
  • stateで応答の正当性を確かめ、リダイレクトURIを完全一致で照合している
  • スコープを必要最小限に絞り、過大な権限を要求していない
  • IDトークンを検証してから本人確認に使い、APIの認可にはアクセストークンを使っている
  • アクセストークンは短命にし、リフレッシュトークンを厳重に保管している

OAuth 2.0とOpenID Connectは、名前が並んで登場しますが、OAuthが担うのはアクセスの委譲という認可、OIDCが担うのは本人確認という認証です。SNSログインや「〜で続ける」の裏側では、この二つが役割を分け合いながら、パスワードを渡さずに安全なサインインを成り立たせています。実装では、認可コードフローとPKCEを土台に、stateとリダイレクトURIの照合で経路を守り、スコープを絞り、トークンごとの役割を崩さないことが要点になります。いま自分が扱っているのが認可なのか認証なのかを問い直しながら設計すると、典型的な落とし穴を避けられます。

出典・参考

この記事をシェア

関連する記事

セキュアコーディング

JWTの仕組みと実装の落とし穴。署名検証とアルゴリズム混同を基礎から

JWT(JSON Web Token)の構造と署名の役割を基礎から整理します。header.payload.signatureの3パート、HS256とRS256の違い、alg:noneやアルゴリズム混同といった検証まわりの落とし穴、有効期限と失効の設計、保存場所によるXSS/CSRFの注意点まで、一次仕様に沿って実装者向けに解説します。