OpenID Connect
OAuth 2.0の上に、利用者の認証の結果をアプリケーションへ安全に伝える仕組みを加えた標準仕様である。ログインの結果は、発行者が署名したIDトークンとして受け渡される。
イマシルちゃんの解説
ほかのサービスのアカウントを使って、別のアプリにログインできることがあるんだ。
その裏でよく使われているのが、OpenID Connectという標準規格だよ。
権限を任せるためのOAuth 2.0を土台にして、「誰がログインしたか」を伝える部分を足したものなの。
利用者がアカウントを管理する事業者の画面でログインすると、アプリはその結果を表すIDトークンというデータを受け取るんだ。
そこには利用者の識別子や、どのアプリ向けにいつ発行されたかが書かれているよ。
アプリは署名や宛先を確かめてから、その人としてログインさせるの。
アプリごとにパスワードを預けなくても、本人確認を信頼できる相手に任せられるのが便利なんだ。
この解説はわかりやすかったですか?
より深い解説
定義と位置付け
OpenID Connectは、OpenID Foundationが定めた仕様群である。中心となるOpenID Connect Core 1.0は、OAuth 2.0の手順をそのまま使いながら、ログインした利用者の情報を伝える方法を加える。OAuth 2.0は権限の委譲のための枠組みであり、「誰がログインしたか」をアプリケーションへ伝える形式は定めていない。その不足を埋めるのがこの仕様である。
利用者の本人確認を担当し、その結果を発行する側をOpenIDプロバイダ(OP)と呼ぶ。OAuth 2.0の認可サーバに、利用者を認証して結果を発行する機能を加えたものに当たる。結果を受け取ってログインに使うアプリケーションをリライングパーティ(RP)と呼ぶ。こちらはOAuth 2.0のクライアントに当たる。
| OpenID Connectの呼び方 | OAuth 2.0での役割 | 例 |
|---|---|---|
| エンドユーザー | リソースオーナー | ログインする利用者 |
| OP | 認可サーバ | アカウントを管理する事業者のログイン基盤 |
| RP | クライアント | 外部のアカウントでのログインを受け付けるWebアプリケーション |
利用者のパスワードを受け取って照合するのはOPだけである。RPはパスワードを扱わず、OPが発行した結果を検証して利用者を受け入れる。複数のRPが同じOPを信頼すれば、シングルサインオンを実現できる。
OAuthとの役割分担
OAuth 2.0とOpenID Connectは、同じ通信の流れの中で別の問いに答える。
| 観点 | OAuth 2.0 | OpenID Connect |
|---|---|---|
| 答える問い | このアプリに何を許したか | 誰が、いつ、どの方法でログインしたか |
| 中心となるトークン | アクセストークン | IDトークン |
| トークンの宛先 | APIを提供するリソースサーバ | ログインを受け付けるRP自身 |
| トークンの形式 | 規定なし | 規定された署名付きの形式 |
アクセストークンはAPIに見せるためのもので、RPが中身を読んで利用者を判断することは想定されていない。逆に、IDトークンをAPIへの権限の証明として送るのも用途違いである。二つのトークンを取り違えないことが、安全な実装の前提になる。
OAuth 2.0だけでログインを作ると、別のアプリケーション向けに発行されたアクセストークンを持ち込まれる危険がある。IDトークンには「どのRP向けに発行したか」が書かれているため、RPは自分宛てでないものを拒否できる。
ログインの流れ
Webアプリケーションで基本となる認可コードフローを例にする。全体はOAuth 2.0の認可コードグラントと同じで、違いは要求に付ける値と、トークンエンドポイントから受け取るものにある。
- 利用者がRPで「外部アカウントでログイン」を選ぶ。
- RPはブラウザをOPの認可エンドポイントへリダイレクトする。要求のスコープには必ずopenidを含め、推測できない値を
nonceパラメータに付ける。 - OPは利用者にログインさせ、必要なら情報の提供について同意を得る。
- OPはブラウザをRPのリダイレクトURIへ戻し、認可コードを渡す。
- RPのサーバは、認可コードをOPのトークンエンドポイントへ送る。
- OPはアクセストークンに加えて、IDトークンを返す。
- RPはIDトークンを検証し、問題がなければ利用者のログインセッションを作る。
- 必要に応じて、アクセストークンを使ってOPから利用者の属性を取得する。
手順2の要求は次のようになる。値はすべて説明用である。
GET /authorize?response_type=code
&scope=openid%20profile%20email
&client_id=photo-print
&redirect_uri=https://print.example/callback
&state=af0ifj...
&nonce=n-0S6_W...
Host: id.example.jp
スコープにこの値がなければ、OpenID Connectの要求として扱われない。prompt=login を付けると、OPに利用者の再ログインを求められる。max_age を付けると、最後のログインからの経過時間が指定を超えた場合に再ログインさせられる。このときIDトークンには、ログインした時刻を示す auth_time が入る。
署名付きトークンの構造
IDトークンはJWT(JSON Web Token)の形式で表す。この形式は、ヘッダー・ペイロード・署名の三つをbase64urlで符号化し、ピリオドでつないだ文字列である。ヘッダーには署名アルゴリズムと鍵の識別子を、ペイロードには利用者や発行に関する情報(クレーム)を入れる。符号化は暗号化ではないため、署名だけの場合は誰でも中身を読める。
ペイロードの例を示す。値は説明用である。
{
"iss": "https://id.example.jp",
"sub": "u-7f3a91c2",
"aud": "photo-print",
"exp": 1767226200,
"iat": 1767225600,
"nonce": "n-0S6_W...",
"auth_time": 1767225590
}
主なクレームの意味は次のとおりである。最初の五つはどのフローでも必須である。
| クレーム | 意味 | 検証での使い方 |
|---|---|---|
| iss | 発行者(OP)の識別子 | 信頼しているOPと完全に一致するか |
| sub | OPの中で利用者を識別する値 | RPの利用者と対応付ける |
| aud | このトークンを受け取るRP | 自分のクライアントIDを含むか |
| exp | 有効期限 | 現在時刻が期限前か |
| iat | 発行時刻 | 古すぎる発行を拒否する目安にする |
| nonce | 認可要求に付けた値 | 自分が送った値と一致するか |
| auth_time | 利用者がログインした時刻 | 再ログインを求めた条件を満たすか |
時刻は1970年1月1日0時(UTC)からの秒数で表す。この例の iat は2026年1月1日9時(日本時間)に当たり、exp との差は次のとおりである。
有効期間 = 1,767,226,200 − 1,767,225,600 = 600秒 = 10分
利用者を識別する値は、そのOPの中で一意で、ほかの利用者に再割り当てされない。長さは最大255文字のASCII文字と定められている。一意性はOPごとに保証されるので、RPは発行者の識別子との組で利用者を識別する。メールアドレスは変更や再利用があり得るため、利用者を識別する鍵には向かない。
受け取った側の検証手順
RPは、IDトークンを受け取っただけでログインさせてはならない。OpenID Connect Core 1.0の3.1.3.7節は、認可コードフローでの検証手順を定めている。主な確認は次の順に行う。
issが、事前に知っているOPの識別子と完全に一致するか確かめる。audに自分のクライアントIDが含まれているか確かめる。信頼していないほかの宛先が含まれていれば拒否する。- 署名を、OPが公開している鍵で検証する。
- 現在時刻が
expより前であることを確かめる。 - 認可要求で
nonceを送ったなら、トークン内の値が送った値と一致するか確かめる。
トークンエンドポイントからTLSで直接受け取ったIDトークンでは、TLSによるサーバの検証を署名の検証の代わりにできると定められている。ブラウザ経由で受け取る場合は、必ず署名を検証する。IDトークンにはJWSによる署名が必須であり、署名なし(alg が none)が許されるのは限られた条件だけである。
iat の許容範囲はRPごとに決める。サーバの時計がずれていると、正しいトークンを期限切れと判断したり、発行時刻が未来だと判断したりすることがある。RPのサーバもNTPなどで時刻を合わせておく。
使い回しを防ぐ値
nonceは、RPのログインセッションとIDトークンを結び付け、リプレイ攻撃を防ぐための値である。RPは推測できない乱数を作って認可要求に付け、OPはその値をIDトークンにそのまま入れる。RPは、戻ってきたIDトークンの値が自分の送った値と一致するかを確かめる。
一致しないIDトークンは、別のログインのために発行されたものである。攻撃者が以前に入手したIDトークンを差し込んでも、今回のログインで送った値と一致しないため拒否できる。値には推測されないだけの十分な乱雑さが必要とされている。
実装の例として、乱数をHttpOnly属性付きのCookieに保存し、そのハッシュ値を認可要求に付ける方法が示されている。戻ってきたIDトークンの値と、Cookieから計算したハッシュ値を比べればよい。
この値は、IDトークンを認可エンドポイントから直接受け取るフローでは必須、認可コードフローでは任意である。state はOAuth 2.0のパラメータで、認可応答が自分の要求に対するものかを確かめる。目的は近いが、この値はIDトークンの中に入って署名で保護される点が異なる。
利用者の属性を取得する
IDトークンに入っていない氏名やメールアドレスなどは、OPのUserInfoエンドポイントから取得できる。このエンドポイントはOAuth 2.0で保護されたAPIであり、RPはトークンエンドポイントで受け取ったアクセストークンをAuthorizationヘッダーに Bearer 方式で付けて要求する。応答はJSONで返る。
取得できる情報の範囲は、認可要求のスコープで指定する。
| スコープ | 主に得られる情報 |
|---|---|
| profile | 氏名、ニックネーム、プロフィールの基本情報 |
| メールアドレスと、それが確認済みかどうか | |
| address | 住所 |
| phone | 電話番号と、それが確認済みかどうか |
UserInfoの応答に含まれる sub は、IDトークンの sub と完全一致することを確かめなければならない。一致しない場合は、その応答を使ってはならない。別の利用者向けのトークンにすり替えられ、他人の情報を受け取る攻撃を防ぐためである。
フローの種類
OpenID Connectには三つのフローがあり、認可要求の response_type で選ぶ。
| フロー | response_typeの値 | IDトークンの受け取り方 |
|---|---|---|
| 認可コードフロー | code | トークンエンドポイントから受け取る |
| インプリシットフロー | id_token、id_token token | 認可エンドポイントからブラウザ経由で受け取る |
| ハイブリッドフロー | code id_token など | 両方のエンドポイントから受け取る |
ブラウザ経由でトークンを受け取る方式は、URLからの漏えいや差し替えの危険が大きい。RFC 9700は、認可応答でアクセストークンを受け取る方式を避け、認可コードを使うよう求めている。新しく作るWebアプリケーションやスマートフォンのアプリでは、認可コードフローにPKCEを組み合わせるのが基本になる。
設定情報と署名鍵の公開
RPがOPを使うには、エンドポイントのURLや署名の検証に使う公開鍵を知る必要がある。OpenID Connect Discovery 1.0では、OPの識別子に /.well-known/openid-configuration を付けたURLで、これらの設定情報をJSONとして公開する。
| 項目名 | 内容 |
|---|---|
| issuer | OPの識別子。IDトークンの iss と一致する |
| authorization_endpoint | 認可エンドポイントのURL |
| token_endpoint | トークンエンドポイントのURL |
| userinfo_endpoint | UserInfoエンドポイントのURL |
| jwks_uri | 署名の検証に使う公開鍵の集合(JWK Set)を置くURL |
OPは署名鍵を定期的に交換することがある。JWTのヘッダーにある鍵の識別子(kid)を見て、公開鍵の集合から対応する鍵を選ぶ。RPが古い鍵を保存したまま更新しないと、鍵の交換後に検証が失敗する。
SAMLとの比較
企業のシングルサインオンでは、SAMLも広く使われる。どちらもログイン結果を発行者の署名付きで別のサービスへ伝える点は共通である。
| 観点 | OpenID Connect | SAML |
|---|---|---|
| 結果を表す形式 | JWT(JSON) | XMLのアサーション |
| 土台 | OAuth 2.0 | 独自のプロトコルとバインディング |
| APIの権限 | アクセストークンで同時に得られる | 主な目的に含まれない |
| 多い用途 | Webアプリケーション、スマートフォンのアプリ | 企業向けのWebアプリケーション |
JSONとHTTPのAPIで扱いやすいため、スマートフォンのアプリやAPIと組み合わせる場面ではOpenID Connectが選ばれやすい。
設計と運用
RPを設計するときは、OPを信頼する範囲と、検証をどこで行うかを決める。
- IDトークンの検証は、ライブラリを使う場合も、iss・aud・署名・期限・nonceがすべて確認されているかを点検する
- 利用者の識別には
issとsubの組を使い、メールアドレスや表示名を使わない - 必要な属性だけをスコープで要求し、受け取った個人情報の保存範囲を決める
- OPの設定情報と公開鍵を定期的に取り直し、鍵の交換に追従する
- RP側のログインセッションの期限と、OP側のログイン状態は別のものとして扱う
最後の点は誤解しやすい。RPでログアウトしても、OPにログインしたままなら、次回は利用者の操作なしにログインできることがある。逆にOPでログアウトしても、RPのセッションが自動的に終わるとは限らない。両者を連動させるには、ログアウトのための別の仕様を使う。
障害の切り分け
ログインの失敗は、OPとの通信の段階か、IDトークンの検証の段階かで分けて調べる。
| 現象 | 考えられる原因 |
|---|---|
| issの不一致で拒否される | 設定したOPの識別子と表記が異なる(末尾のスラッシュの有無など) |
| audの不一致で拒否される | 別のクライアントIDで登録した設定を使っている |
| 署名の検証だけが急に失敗する | OPの鍵の交換に、保存した公開鍵が追従していない |
| 期限切れや発行時刻の異常で拒否される | RPのサーバの時刻がずれている |
| nonceの不一致で拒否される | ログイン開始時の値を保存したCookieやセッションが失われている |
| 属性の取得に失敗する | アクセストークンの期限切れ、必要なスコープを要求していない |
署名や時刻の問題は、OPの画面では正常にログインできているのに、RPに戻った後だけ失敗する形で現れる。どの段階まで進んだかをRPのログで確かめると、原因を絞り込める。
午後での問われ方
- OAuth 2.0との違いを、トークンの宛先と答える問いの違いから説明させる
- IDトークンのクレームのうち、RPが検証すべき項目とその理由を答えさせる
- nonceやstateが防ぐ攻撃と、検証する主体を答えさせる
- シーケンス図の中で、OP・RP・利用者のブラウザの間を流れるメッセージを特定させる
- 時刻のずれや鍵の交換によるログイン障害の原因を、ログから判断させる
- SAMLとの使い分けや、シングルサインオンでのログアウトの扱いを考えさせる
参考資料
- OpenID Connect Core 1.0
- OpenID Connect Discovery 1.0
- OpenID Connect Basic Client Implementer's Guide 1.0
- RFC 6749:The OAuth 2.0 Authorization Framework
- RFC 9700:Best Current Practice for OAuth 2.0 Security