OIDC
OpenID Connect ・ おーあいでぃーしー
OAuth 2.0に宛先つきのログイン証明(IDトークン)を足した、「◯◯でログイン」を支える認証の標準。
✦ 30秒で試す — 航海ログインボタン1つの裏で、5つの門が順に開いているその証明を家計簿アプリに見せると、アリスとして入れる?予想してみる概要
OIDC(OpenID Connect)は、OAuth 2.0 の上に「本人確認」の層を載せた標準プロトコルです。「Google でログイン」「Apple でサインイン」といったボタンの裏側で動いているのがこの OIDC で、あなたが Google で済ませたログインの結果を、別のアプリが安全に受け取れるようにします。パスワードを各サービスに預ける代わりに、信頼できる一箇所(IdP)で認証を済ませ、その証明書を受け取る仕組みと言い換えられます。
鍵になるのは、OAuth が認可(何をしてよいかの許可)のプロトコルであって、認証(誰であるかの確認)の仕組みではない、という区別です。OAuth のアクセストークンは「この権限で API を呼んでよい」という切符にすぎず、「誰が・どのアプリにログインしたのか」は保証しません。OIDC はこの欠けていた部分を、ID トークンという成果物を1枚足すことで補いました。ID トークンの実体は OP(認証を担うサーバ)が署名した JWT で、「どの OP が・誰を・いつ・どのアプリ向けに認証したか」が書かれた、検証できるログインの証明書です。
いまでは、コンシューマ向けのソーシャルログインから企業の SSO 基盤まで、新しく作られる認証連携の多くが OIDC を採用しています。Web の認証を学ぶうえで、OAuth とセットで避けて通れない語彙です。
なぜ生まれたか
OAuth 2.0 が普及すると、多くのサービスが「アクセストークンが取れた、そのトークンでプロフィール API を呼べた、だからこの人がログインした」とみなして、OAuth をログインに流用し始めました。しかしこれは危険な誤用でした。アクセストークンには「どのアプリ向けに発行されたか」を受け手が確かめる標準の手段がありません。攻撃者が自分の運営する別のアプリで、被害者から正規の手順でトークンを受け取り、それを狙ったアプリへ持ち込むと、被害者としてログインできてしまいます。これがトークン置き換え攻撃です。問題は「OAuth は認可の仕組みで、認証の仕組みではない」ことそのものにありました。各社が独自の回避策を足した結果、「Facebook でログイン」「Google でログイン」の実装は、それぞれ微妙に違う方言になっていきました。
それ以前にも認証連携の標準はありました。2005年に生まれた OpenID(2007年に OpenID 2.0)は、URL を ID として使う分散型のログインでしたが、独自の発見手順と署名方式が複雑で、API の認可とは別の世界の仕組みでした。企業向けの SAML は、署名付き XML の処理が重く、モバイルアプリや API 中心の開発とは相性がよくありませんでした。そこで OpenID Foundation は、すでに広く実装されていた OAuth 2.0 の枠組みをそのまま土台にし、その上に「認証結果を標準の形で受け渡す」薄い層を定義しました。2014年2月に最終版となった OpenID Connect Core 1.0 です。認可のフローに、署名付きで宛先の入った ID トークンを1枚足すだけで、方言だらけだったソーシャルログインが相互に運用できる標準になりました。
詳細
OAuth に何を足したのか
OIDC のフローは、OAuth の認可コードフローとほぼ同じ形をしています。違いは3点です。第一に、認可リクエストの scope に openid を含めて「これはログインの要求である」と宣言すること。第二に、トークンエンドポイントの応答に、アクセストークンと並んで ID トークンが返ること。第三に、利用者のプロフィール(氏名・メールアドレスなど)を返す UserInfo エンドポイントが標準化されていることです。役割の名前もログインの文脈に合わせて、認可サーバは「OpenID プロバイダ(OP)」、クライアントは「リライング・パーティ(RP、依拠当事者)」と呼ばれます。組織の IdP が OP の役を担うのが典型です。
ブラウザを1人の参加者として置いて、フローを追ってみます。
(1)利用者がログインボタンを押すと、RP は推測できない乱数 state と nonce を作って、このブラウザとのセッションに保存します。(2)RP はブラウザを OP の認可エンドポイントへリダイレクトさせます。URL には scope=openid、自分の client_id、戻り先、state と nonce が載ります。(3)OP が本人を認証します。パスワードを入力する相手は OP だけで、多要素認証を求めるのも OP です。(4)OP は認可コードを添えてブラウザを RP へ戻し、ブラウザがそれを RP へ届けます。ここまでがブラウザを経由する表口(フロントチャネル)です。(5)RP はサーバから直接 OP のトークンエンドポイントを呼んで、コードを ID トークンとアクセストークンに交換します。(6)RP は ID トークンを検証し、通れば自分のセッションを開始します。ログインを成立させるのは OP ではなく、RP 自身の検証です。
ID トークンの中身と、RP が通す門
ID トークンは署名付きの JWT で、ペイロードに標準のクレーム(主張)が並びます。発行者 iss、利用者の識別子 sub、宛先のアプリ aud(RP の client_id)、有効期限 exp と発行時刻 iat、RP が送った nonce、実際に認証が行われた時刻 auth_time などです。どのように認証したか(パスワードだけか、多要素か)を示す amr や acr が入ることもあります。
RP はこれを、下の図の順に門をくぐらせて確かめます。まず署名です。ヘッダの kid(鍵の ID)で OP の公開鍵の束(JWKS)から1本を選び、公開鍵暗号の署名を検証します。これで「OP が発行し、途中で書き換えられていない」ことが分かります。次に iss が期待した OP か、aud に自分の client_id が入っているか、exp が過ぎていないか、nonce がこのブラウザのセッションに保存しておいた値と一致するかを確かめます。ひとつでも落ちたらログインさせません。sub は門ではなく、すべて通った後に「誰か」を決めるための値です。
このうち、OIDC が OAuth に足したものの核心は aud の門です。アクセストークンは「API を呼んでよい」という持参人型の切符で、受け手の RP から見ると、自分向けに発行されたのか、よそのアプリ向けに発行されたのかが分かりません。ID トークンは宛先が署名の内側に書かれているので、よそのアプリ向けに発行された本物のトークンを持ち込まれても、aud が一致せずに弾けます。下の部品で、攻撃者になってトークンを持ち込み、2種類の RP の反応の違いを確かめてください。
アリス(あなた)は、攻撃者が運営する占いアプリで「Googleでログイン」を押しました。ブラウザの「同意」チップをドラッグして、ログインと同意を受け付ける相手へ運んでください(タップするとおまかせで運びます)。
nonce と state: それぞれ何を結びつけるのか
aud が「どのアプリ向けか」を縛るのに対して、nonce は「このブラウザの、このログイン要求の結果か」を縛ります。RP はログインを始めるたびに使い捨ての nonce を作ってブラウザとのセッションに保存し、認可リクエストに載せます。OP はそれを ID トークンの中に入れて署名します。RP は、戻ってきた ID トークンの nonce が、そのブラウザのセッションに保存した値と一致するかを確かめます。攻撃者がよそで手に入れた ID トークン(たとえば、攻撃者自身がこの RP にログインしたときのもの)を被害者のブラウザの戻り道に差し込んでも、nonce はその被害者のセッションの値と一致しません。過去のトークンを使い回すリプレイも同じ門で止まります。
state はこれと似ていますが、守る場所が違います。state は、リダイレクトで戻ってきた「コード付きの戻り」自体が、このブラウザが始めた要求への応答かを確かめる値で、CSRF 型の攻撃(攻撃者の認可コードを被害者に踏ませる)を防ぎます。さらに、コードの横取りや差し込みには PKCE が効きます。PKCE の仕組みは OAuth の記事の「PKCE: シークレットを持てないアプリのために」の節で説明しています。SPA やスマホアプリから OIDC を使うときも、PKCE 付きの認可コードフローを使うのが現在の定石です。
エコシステムを支える周辺仕様
OIDC が広く普及した理由のひとつは、運用を楽にする周辺仕様が最初から揃っていたことです。Discovery では、OP の各エンドポイントや対応機能を /.well-known/openid-configuration という URL から機械可読な JSON で取得できます。RP は OP の発行者 URL さえ知っていれば、設定の大半を自動化できます。署名検証用の公開鍵は JWKS エンドポイントで公開され、ID トークンのヘッダの kid で使う鍵を選びます。OP が鍵をローテーションしても、RP は新しい kid を見て JWKS を取り直すだけで追従できます。このほか、クライアント登録を自動化する Dynamic Client Registration や、RP からのログアウト、複数の RP への一斉ログアウトを扱うログアウト系の仕様も定義されています。
実務での位置づけと落とし穴
Auth0・Okta・Microsoft Entra ID・Keycloak といった IdP 製品はどれも OIDC に対応しており、自社サービスに「◯◯でログイン」を組み込む場面でも、社内システムを SSO にまとめる場面でも、まず OIDC が第一候補になります。企業向けでは先行規格の SAML も現役ですが、モバイルや SPA、API 連携を含む新規構築では OIDC が標準的な選択です。
典型的な落とし穴は4つあります。第一に、ID トークンとアクセストークンの取り違えです。ID トークンは「RP がログインを確認するためのもの」で、API を呼ぶのはアクセストークンです。逆に、リソースサーバが ID トークンを API の認可に受け付けるのも誤りです。第二に、検証の省略です。署名・iss・aud・exp・nonce のどれかを飛ばすと、上の図の対応する攻撃がそのまま通ります。「署名が本物なら大丈夫」ではなく、本物だが自分宛てではないトークンがあり得る、というのが部品で見たことです。自前で実装せず、実績のあるライブラリに検証を任せるのが基本です。第三に、利用者の識別にメールアドレスを使うことです。メールアドレスは変わりますし、OP によっては確認されていないこともあります。アカウントの紐付けは iss と sub の組で行います(sub は OP の中でだけ一意です)。第四に、ID トークンの有効期限をログインの長さと取り違えることです。ID トークンはログインした瞬間の証明で、その後のログイン状態は RP 自身のセッションが持ちます。長く続くアクセスにはリフレッシュトークンを組み合わせます。
試問
アリスが、攻撃者の運営する占いアプリに「Googleでログイン」するところから始まります。同意のチップを OP(Google)へ運ぶと、占いアプリはアリスのアクセストークンと IDトークン(宛先 aud=占い)の2枚を手にします。そこから攻撃者として、トークンをつかんで家計簿アプリの2つの実装(旧式と OIDC)へ運べます。
アリス(あなた)は、攻撃者が運営する占いアプリで「Googleでログイン」を押しました。ブラウザの「同意」チップをドラッグして、ログインと同意を受け付ける相手へ運んでください(タップするとおまかせで運びます)。
