+ 作る
図を読み込んでいます…
1 / 14
図でつながる 認証と暗号の基礎 / 全11章

OIDC ― OAuth に「誰がログインしたか」を伝える層を足す

発行
かえで
版
第2版
改訂
分量
14コマ・5,615字

「〜でログイン」の裏で何が起きているのか。OAuth の上に OIDC を重ね、紫の IDトークンと橙のアクセストークンの行き先を色で追いながら、nonce・検証・iss と sub・UserInfo を順に見ていきます。

ボタンを押したのは誰?

第9章では OAuth を使って、アオイが自分の写真へのアクセスをアプリに委ねる流れを見ました。この章はその続きです。

多くのアプリには「Google でログイン」のようなボタンがあります。アオイがこのボタンを押すと、画面はいったんログイン提供者へ移り、しばらくするとアプリに戻ってきて、ログインした状態になります。会員登録の手間を省けるので、よく見かける仕組みです。

このときアプリが本当に知りたいのは、アオイの写真ではありません。「今ボタンを押したのは、どの提供者のもとの、どの利用者か」です。図の吹き出しが、その問いです。ログインの仕組みを借りたいアプリにとって、知りたいことはこの一点に尽きます。

  • ボタンを押したのは誰?
  • ブラウザ
  • 守られている
  • ログイン提供者
  • アプリ
  • 人
  • 押す
  • 提供者でログイン
  • アオイ
  • 押したのは誰?
  • アクセストークン
  • 知りたいのは写真ではなく、「誰か」

アクセストークンは入館証、手紙ではない

OAuth の流れを終えると、アプリはアクセストークンを受け取ります。図の橙のカードです。これは写真 API に見せる入館証のようなもので、API は「このトークンなら、この範囲の写真を見せてよい」と判断します。

ところが入館証は、アプリに宛てた手紙ではありません。OAuth では、アクセストークンはアプリから見て意味の分からない文字列であることが多く(RFC 6749)、そこに「誰が、いつ、どの提供者で認証されたか」が書いてあるとは決まっていません。API を呼べたことを「ログインした証拠」と見なすと、別のアプリ向けに出された入館証を持ち込まれたときに見分けられません。

アプリに必要なのは、自分に宛てて「この人はこう認証されました」と書かれた手紙です。それを OAuth に足すのが OpenID Connect(OIDC)です。

  • アクセストークンは入館証、手紙ではない
  • ブラウザ
  • 守られている
  • ログイン提供者
  • アプリ
  • 人
  • でも、誰なの?
  • アオイ
  • 写真
  • 写真 API
  • アオイの写真を預かる
  • API を呼ぶ
  • アクセストークン
  • 入館証は API のためのもの。アプリ宛ての「誰か」は書かれていない

OAuth の上に OIDC という層を足す

OIDC は、OAuth 2.0 を置き換える別の仕組みではありません。図のように、OAuth の流れを土台にして、その上に一枚の層を重ねたものです。認可コードを受け取り、それをトークンと交換するという手順は、第9章とほとんど同じです。

上の層で増える主なものが、紫の IDトークン です。「どの提供者が、どの利用者を、いつ認証したか」をアプリに宛てて書いた手紙で、形式には第8章の JWT が使われます。署名が付いているので、アプリは途中で書き換えられていないことを確かめられます。

役割を分けるとこうなります。下の OAuth は「アクセスを委ねる」ための土台で、橙のアクセストークンはこちらに属します。上の OIDC は「認証の結果を伝える」ための層です。仕様は OpenID Foundation の OpenID Connect Core 1.0 にまとまっています。

  • OAuth の上に OIDC という層を足す
  • OpenID Connect(OIDC)
  • 誰がログインしたかをアプリへ伝える
  • IDトークン
  • 形式は JWT(第8章)
  • OAuth 2.0(第9章)
  • アクセスを委ねる土台
  • アクセストークン
  • 流れは OAuth のまま。上の層で IDトークンが増える

呼び名は RP と OP

OIDC では、登場する者に独自の呼び名があります。ログインを受け入れるアプリは RP(Relying Party)です。「認証の結果を信頼して、それに頼る側」という意味です。利用者を認証して IDトークンを発行する提供者は OP(OpenID Provider)です。図の二つの枠に、その呼び名が付きました。

OAuth の言葉に直すと、RP はクライアント、OP は認可サーバーにあたります。同じアプリを、OAuth の目で見ればクライアント、OIDC の目で見れば RP と呼ぶわけです。写真 API は OAuth のリソースサーバーで、ログインそのものには関わりません。ログインだけが目的なら、写真 API がなくても OIDC は成り立ちます。

ここからは、第9章の Authorization Code Flow(PKCE 付き)に OIDC が何を足すかを、差分として追っていきます。

  • 呼び名は RP と OP
  • ブラウザ
  • 守られている
  • ログイン提供者
  • OP:OpenID Provider
  • アプリ
  • 利用者を認証する側
  • RP:Relying Party
  • 人
  • ログインを受け入れる側
  • アオイ
  • 写真
  • 写真 API
  • アオイの写真を預かる
  • OAuth の言葉では、RP はクライアント、OP は認可サーバー

openid と nonce を付けて頼む

アプリはまず、アオイのブラウザを OP の認可エンドポイントへ送ります。第9章との違いは、scope に openid を含めることです。この値が「OIDC として、ログインの結果も返してほしい」という合図になり、OP は IDトークンを発行するようになります。名前やメールアドレスも欲しいなら、profile や email も並べます。

もう一つ、アプリは nonce(一度だけ使う乱数)を作ってリクエストに付け、図のように手元にも覚えておきます。OP はこの値を IDトークンの中にそのまま書き込みます。後で IDトークンを受け取ったとき、覚えておいた値と同じなら「自分が今出したリクエストへの答えだ」と分かり、別の場面で盗まれた IDトークンを差し込まれるのを防げます。

PKCE の code_verifier も手元に残しますが、役割は別です。code_verifier は認可コードの交換を守り、nonce は IDトークンとリクエストを結び付けます。コードフローでは nonce は必須ではありませんが、付けたなら照合は必ず行います(認証リクエストの説明)。

  • openid と nonce を付けて頼む
  • ブラウザ
  • 守られている
  • ログイン提供者
  • OP:OpenID Provider
  • アプリ
  • RP:Relying Party
  • openid+nonce
  • 人
  • 認可コード
  • nonce = n1 を覚える
  • アオイ
  • code_verifier
  • どちらもアプリの手元に残す
  • 写真
  • 写真 API
  • アオイの写真を預かる
  • nonce は IDトークン用、code_verifier はコード交換用

ログインして、認可コードが戻る

OP の画面で、アオイはパスワードやパスキーなどでログインします。図ではアオイが OP のところへ移っています。アプリはこの間、アオイのパスワードを一度も見ません。認証をするのは OP だけです。

ログインが済み、アオイがアプリへの情報の提供に同意すると、OP はアオイのブラウザをアプリへ戻し、そのとき 認可コード を渡します。図の黄色いカードが OP からアプリへ移ります。認可コードは引換券のようなもので、それだけではまだ何もできません。アプリは第9章と同じく、戻ってきた state が自分の送った値と同じかも確かめます。

ここまでは第9章のコードフローとまったく同じです。違いが表に出るのは、次の交換の場面です。

  • ログインして、認可コードが戻る
  • ブラウザ
  • 人
  • 守られている
  • ログイン提供者
  • OP:OpenID Provider
  • アプリ
  • RP:Relying Party
  • アオイ
  • ログイン OK
  • アクセストークン
  • IDトークン
  • nonce = n1 を覚える
  • code_verifier
  • ブラウザ経由
  • どちらもアプリの手元に残す
  • 写真
  • 写真 API
  • 認可コード
  • アオイの写真を預かる
  • ここまでは第9章のコードフローと同じ

交換すると、紫と橙の2枚が返る

アプリは認可コードと code_verifier を、OP のトークンエンドポイントへ直接送ります。図では黄色のコードが OP へ渡って消えます。OP が正しいと確かめると、応答には橙のアクセストークンに加えて、紫の IDトークンが入っています。scope に openid を含めたからです。

二つはここから行き先が分かれます。紫の IDトークンは、このアプリ自身が開いて読むための手紙です。宛先はアプリで、ほかへは渡しません。橙のアクセストークンは、写真 API や後で出てくる UserInfo を呼ぶための入館証で、アプリは中身を読まずにそのまま見せます。

色を分けて追うのは、この二つを「どちらもログインの証拠」とひとまとめにしないためです。この先の図でも、紫と橙がどこへ行くかに注目してください。

  • 交換すると、紫と橙の2枚が返る
  • ブラウザ
  • 守られている
  • ログイン提供者
  • OP:OpenID Provider
  • アプリ
  • RP:Relying Party
  • コード+verifier
  • 人
  • トークン応答
  • code_verifier
  • 認可コード
  • nonce = n1 を覚える
  • アオイ
  • 写真
  • 写真 API
  • IDトークン
  • アプリが読む
  • アオイの写真を預かる
  • アクセストークン
  • API 用
  • 紫はアプリ宛ての手紙、橙は API への入館証

IDトークンの中身は JWT

IDトークンは、第8章で見た JWT です。ヘッダー・ペイロード・署名の三つを点でつないだ文字列で、ペイロードには クレーム(中身の項目)が並びます。図は、そのうち主なものです。

iss(issuer)は発行した OP の識別子で、https から始まる URL です。sub(subject)は、その OP の中で利用者を表す番号で、同じ OP なら人ごとに変わりません。aud(audience)は宛先で、アプリの client_id が入ります。exp は有効期限、iat は発行した時刻で、どちらも1970年からの秒数で書かれます。nonce には、アプリがリクエストに付けた値がそのまま入ります。

一番下の署名は、OP が自分の秘密鍵で付けたものです。アプリは OP の公開鍵で確かめるので、途中で一文字でも書き換えられていれば気づけます。名前やメールアドレスが入ることもありますが、まずはこの六つが骨組みです。

  • IDトークンの中身は JWT
  • IDトークン
  • JWT(第8章)
  • iss
  • 発行した OP(https の URL)
  • sub
  • OP の中での利用者の番号
  • aud
  • 宛先のアプリ(client_id)
  • exp
  • 有効期限
  • iat
  • 発行した時刻
  • nonce
  • アプリが送った値(n1)
  • 署名:OP が秘密鍵で付ける

アプリ自身が IDトークンを検証する

IDトークンを受け取ったアプリは、中身を信じる前に自分で確かめます。図で緑の印が付いた五つの点です(IDトークンの検証)。

まず署名を、OP が公開している公開鍵の束(JWKS、第8章)で確かめます。次に、iss が自分の想定した OP と完全に一致するか、aud に自分の client_id が入っているかを見ます。aud を見ないと、別のアプリ宛てに発行された IDトークンを持ち込まれても受け入れてしまいます。さらに、exp を過ぎていないか、nonce が覚えておいた n1 と同じかを確かめます。

どれか一つでも合わなければ、ログインは失敗として扱います。aud に複数の値が入る場合など細かな条件もあるので、実装では規格に沿ったライブラリを使い、設定を省かないようにします。base64 を解いて中身を読んだだけでは、検証したことになりません。

  • アプリ自身が IDトークンを検証する
  • IDトークン
  • アプリが確かめる
  • iss
  • 想定した OP と一致するか
  • OK
  • sub
  • 利用者の番号
  • aud
  • 自分の client_id が入っているか
  • OK
  • exp
  • 期限が切れていないか
  • OK
  • iat
  • 発行した時刻
  • nonce
  • 覚えておいた n1 と同じか
  • OK
  • 署名:JWKS の公開鍵で確かめる
  • OK
  • 一つでも合わなければ、ログインは失敗

設定は Discovery 文書で見つける

ここまでの流れには、OP の住所がいくつも出てきました。認可エンドポイント、トークンエンドポイント、署名を確かめる公開鍵の束の場所などです。これをアプリに一つずつ書き込む代わりに、OP は設定を一枚の文書にまとめて公開しています。これが Discovery です(OpenID Connect Discovery 1.0)。

文書の場所は決まっていて、OP の issuer の後ろに /.well-known/openid-configuration を付けた URL です。中身は JSON で、図のように issuer、authorization_endpoint、token_endpoint、userinfo_endpoint、そして jwks_uri などが並びます。jwks_uri の先に、前のページで使った公開鍵の束があります。

OP は署名の鍵をときどき入れ替えますが、アプリが jwks_uri から鍵を取り直せば、設定を書き換えずについていけます。文書に書かれた issuer が、取りに行った先の issuer と一致することも確かめます。

  • 設定は Discovery 文書で見つける
  • 守られている
  • ログイン提供者
  • OP:OpenID Provider
  • ブラウザ
  • アプリ
  • RP:Relying Party
  • OP が公開している設定の文書
  • /.well-known/openid-configuration
  • 最初に読む
  • issuer authorization_endpoint token_endpoint userinfo_endpoint
  • jwks_uri
  • 鍵
  • OP の公開鍵の束へ
  • URL を一つずつ書き込まず、ここから読む

利用者は iss と sub の組で見分ける

IDトークンの sub は、OP の中で利用者を表す番号です。OP は同じ人に同じ sub を出し続け、別の人に使い回しません。ただしその約束は一つの OP の中だけのものです。図のように、提供者 A の「123」と提供者 B の「123」は、別人かもしれません。

そこでアプリは、利用者を iss と sub の組 で覚えます。規格も、利用者を確実に見分けられるのはこの組だけだとしています(クレームの安定性)。データベースには、この二つを並べて保存しておきます。

メールアドレスで見分けたくなるかもしれませんが、メールアドレスは変更できますし、提供者によっては手放されたアドレスが別の人に割り当て直されることもあります。図の二枚のカードはメールアドレスが同じですが、それだけで二つのアカウントを勝手に一つにすると、他人のアカウントを渡してしまうおそれがあります。統合するなら、利用者に両方でログインしてもらうなど、本人の操作を挟みます。

  • 利用者は iss と sub の組で見分ける
  • 守られている
  • 提供者 A
  • 守られている
  • 提供者 B
  • iss = op-a.example
  • ≠
  • iss = op-b.example
  • sub = 123
  • sub = 123
  • email = [email protected]
  • email = [email protected]
  • メールが同じ → 勝手に一つにしない
  • 利用者の鍵 = iss と sub の組
  • 同じ「123」でも、発行した OP が違えば別の人

追加のプロフィールは UserInfo へ

IDトークンには最小限のことしか入っていない場合があります。表示名やプロフィール写真がもっと欲しいとき、アプリは OP の UserInfo エンドポイント を呼びます。このとき見せるのは橙のアクセストークンです。図で、橙のカードが OP の UserInfo へ向かいます。

UserInfo は、アクセストークンで許された範囲(profile や email など)の情報を返します。応答には必ず sub が入っていて、アプリはそれが IDトークンの sub と同じかを確かめます。違っていたら、その応答は使いません(UserInfo の応答)。別人の情報を取り違えないための一手間です。

この間も、紫の IDトークンはアプリの手元に残っています。UserInfo に見せるのは入館証の橙で、手紙の紫ではありません。

  • 追加のプロフィールは UserInfo へ
  • ブラウザ
  • 守られている
  • ログイン提供者
  • OP:OpenID Provider
  • アプリ
  • RP:Relying Party
  • アクセストークン
  • 人
  • UserInfo
  • sub = 123 名前・写真
  • 応答
  • アオイ
  • OK
  • IDトークンと sub が一致
  • 写真
  • 写真 API
  • IDトークン
  • アオイの写真を預かる
  • 橙は OP の UserInfo へ。紫はアプリの手元に残る

IDトークンは API に渡さず、セッションへつなぐ

IDトークンを、写真 API などを呼ぶ入館証として使ってはいけません。IDトークンの aud はこのアプリで、API は宛先ではないからです。宛先の違う手紙を受け取った API が中身を信じてしまうと、別のアプリ向けに発行された IDトークンでも通ってしまいます。API には、その API のために発行されたアクセストークンを見せます。図の赤い矢印は、この使い方をしないという印です。

IDトークンの役目は、ログインの瞬間にアプリへ結果を伝えるところまでです。検証が済んだら、アプリは iss と sub をもとに自分のデータベースから利用者を見つけ、アプリ自身のセッション(ログインした状態を覚えておく仕組み)を始めます。以後のページ移動では、IDトークンではなくこのセッションで「アオイだ」と分かります。

そして、アオイに何を許すかはアプリが決めます。OIDC が伝えるのは「誰か」までです。「この写真を消してよいか」は、第1章で見たとおり認可の仕事で、それはアプリ自身が受け持ちます。

  • IDトークンは API に渡さず、セッションへつなぐ
  • ブラウザ
  • 守られている
  • ログイン提供者
  • OP:OpenID Provider
  • アプリ
  • RP:Relying Party
  • 人
  • iss と sub で利用者を探す
  • アオイ
  • IDトークン
  • NG
  • 写真
  • 写真 API
  • 入館証にしない
  • アオイの写真を預かる
  • アプリのセッション
  • アクセストークン
  • 何を許すかは、アプリ自身の認可で決める(第1章)

まとめ:IDトークンの宛先は写真 API?

この章では、OAuth の上に OIDC の層を足すと何が増えるかを見ました。アプリは scope に openid を含め、nonce を覚えてから流れを始めます。認可コードを交換すると、橙のアクセストークンと並んで紫の IDトークンが返ります。アプリは IDトークンの署名を JWKS の公開鍵で確かめ、iss・aud・exp・nonce を照合し、利用者を iss と sub の組で見分けて、自分のセッションへつなぎます。必要な住所は Discovery 文書から読み、追加のプロフィールはアクセストークンで UserInfo から受け取ります。

では、題の問いです。IDトークンの宛先は写真 API でしょうか。答えは、いいえです。IDトークンの aud に入っているのはログインを受け入れるアプリの client_id で、読むのも検証するのもそのアプリです。写真 API に見せるのはアクセストークンのほうです。

三つの言葉を役割で並べると、OAuth はアクセスを委ねる仕組み、OIDC は認証の結果をアプリへ伝える仕組み、JWT はそのやりとりで使われるトークンの形式です。次の章では、アプリが自分自身のこと(名前や戻り先の URL など)を、どうやって認可サーバーに知らせるかを見ていきます。

  • まとめ:IDトークンの宛先は写真 API?
  • アクセストークン
  • OAuth:アクセスを委ねる(API の入館証)
  • IDトークン
  • OIDC:誰がログインしたかをアプリへ伝える
  • JWT
  • JWT:トークンの形式(第8章)
  • いいえ。IDトークンの宛先は、ログインを受け入れるアプリ
  • 次の章:アプリの自己紹介を、認可サーバーにどう知らせるか

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]