2024年1月8日月曜日

OpenID Providerを作る)まずはOpenID Providerの情報をRelying Partyに提供する

こんにちは、富士榮です。

前回のポストではOpenID Connectの認可コードフローの全体像を書きましたので今回から中身について深掘りしていきたいと思います。


今回は①のディスカバリの部分です。

ディスカバリとは何をするフェーズなのか?ですが、簡単にいうとOpenID Provider自身に関する情報をRelying Partyに伝えることが目標となります。

もちろんRelying Partyに対してOpenID Providerの構成情報を都度細かく伝えても良いのですが、Relying Partyの数が増えてくるとチリも積もるので設定作業が面倒になってきますし、何よりも何らかの構成変更がOpenID Provider側に発生した場合に変更情報を全Relying Partyに伝え直すのは至難の技です。

この課題を解決するためにOpenID ConnectにはOpenID Connect Discoveryという仕様が定められています。

Discoveryの流れ

大まかな流れとしては、以下の2つのフェーズに分かれます。

  1. ユーザの識別子からOpenID ProviderのURLを取得する
  2. OpenID Providerの構成情報を取得する

まず最初のフェーズですが、元々はDiscoveryの仕様はWebfingerの仕様に則ってOpenID ProviderのURLを取得することを想定して策定されていました。

こんな流れです。

  1. Relying Partyにユーザが自身の識別子(例:test@example.jp)を提供する
  2. Relying Partyのユーザの識別子のドメインパート(example.jp)に対応するOpenID Providerを検索する(この部分がWebfinger)
    • 具体的には以下のGETリクエストを投げる
    • https://example.jp/.well-known/webfinger?
      • resource=test@example.jp&
      • rel=http://openid.net/specs/connect/1.0/issuer
  3. 結果として返却されるOpenID ProviderのURLをRelying Partyは取得する

しかしながら、現実問題としてほとんどのRelying Partyではユーザ自身が利用するOpenID Providerを選択するUXとなっているため、このフェーズが利用されることはほとんどありません。(OpenID Providerが利用者の識別子がOpenID Providerのドメイン名と一致していないケースも多くなってきていることもあるんだと思います)

そのため、本命は2番目のフェーズとなる、Relying PartyがあらかじめOpenID ProviderのURLまでは知っているところからスタートする構成情報の取得となっています。

こちらは{OpenID ProviderのURL}/.well-known/openid-configurationというURLへのGETリクエストを投げ込むことでOpenID Providerの構成情報(エンドポイントやサポートする署名アルゴリズムなど)を取得するという仕組みとなっています。

例えば、LINE Loginであれば「https://access.line.me/.well-known/openid-configuration」をGETすると以下のメタデータが返ってきます。

{

    "issuer": "https://access.line.me",

    "authorization_endpoint": "https://access.line.me/oauth2/v2.1/authorize",

    "token_endpoint": "https://api.line.me/oauth2/v2.1/token",

    "revocation_endpoint": "https://api.line.me/oauth2/v2.1/revoke",

    "userinfo_endpoint": "https://api.line.me/oauth2/v2.1/userinfo",

    "scopes_supported": ["openid", "profile", "email"],

    "jwks_uri": "https://api.line.me/oauth2/v2.1/certs",

    "response_types_supported": ["code"],

    "subject_types_supported": ["pairwise"],

    "id_token_signing_alg_values_supported": ["ES256"],

    "code_challenge_methods_supported": ["S256"]

}


実態として.well-known/openid-configurationも提供していないOpenID Providerも存在しますし、返ってくる情報が当てにならないところもあるのでちゃんとドキュメントは読みましょう、という話にしかならないわけですが・・・(ちなみにLINE LoginもIDトークンの署名アルゴリズム/id_token_signing_alg_values_supportedがES256とありますが、実際の署名としてはRS256となっていたりします)

OpenID Providerの構成情報にはどの様なものがあるのか

仕様の「3.  OpenID Provider Metadata」にOpenID Providerが提供するメタデータに記載されている構成情報について解説されています。

ざっくりこんな感じです。

構成情報必須内容備考
issuer必須OpenID Provider自体の識別子
authorization_endpoint必須認可エンドポイントのURL
token_endpointトークンエンドポイントのURLImplicit Flowの場合以外は必須
userinfo_endpoint推奨userInfoエンドポイントのURL
jwks_uri必須jwks_uriエンドポイントのURL
registration_endpoint推奨動的クライアント登録エンドポイントのURL
scopes_supported推奨サポートするscopeパラメータの値
response_types_supported必須サポートするresponse_typeパラメータの値
response_modes_supported任意サポートするresponse_modeパラメータの値
grant_types_supported任意サポートするgrant_typeパラメータの値
acr_values_supported任意サポートするacrパラメータの値
subject_types_supported必須サポートする識別子種別の値
id_token_signing_alg_values_supported必須サポートするid_tokenへの署名アルゴリズム
id_token_encryption_alg_values_supported任意サポートするid_tokenの暗号化アルゴリズム(alg値)
id_token_encryption_enc_values_supported任意サポートするid_tokenの暗号化アルゴリズム(enc値)
userinfo_signing_alg_values_supported任意サポートするuserInfoの署名アルゴリズム
userinfo_encryption_alg_values_supported任意サポートするuserInfoの暗号化アルゴリズム(alg値)
userinfo_encryption_enc_values_supported任意サポートするuserInfoの暗号化アルゴリズム(enc値)
request_object_signing_alg_values_supported任意サポートするリクエストオブジェクト(request_uriからの返却値)の署名アルゴリズム
request_object_encryption_alg_values_supported任意サポートするリクエストオブジェクト(request_uriからの返却値)の暗号化アルゴリズム(alg値)
request_object_encryption_enc_values_supported任意サポートするリクエストオブジェクト(request_uriからの返却値)の暗号化アルゴリズム(enc値)
token_endpoint_auth_methods_supported任意サポートするトークンエンドポイントにおけるクライアント認証方式
token_endpoint_auth_signing_alg_values_supported任意トークンエンドポイントでのクライアント認証にJWTを利用する場合にサポートする署名アルゴリズム
display_values_supported任意サポートするdiplayパラメータの値
claim_types_supported任意サポートするclaimタイプ(分散クレームなどのサポートのうむ)
claims_supported推奨サポートするclaimの種類(sub,emailなど実際に提供するクレームの名前)
service_documentation任意OpenID Providerの説明ページのURL
claims_locales_supported任意サポートするclaimのロケール
ui_locales_supported任意サポートするui_locale
claims_parameter_supported任意OpenID Providerがclaimsパラメータを受け付けるかどうか
request_parameter_supported任意OpenID Providerがrequestパラメータを受け付けるかどうか
request_uri_parameter_supported任意OpenID Providerがrequest_uriパラメータを受け付けるかどうか
require_request_uri_registration任意OpenID Providerが事前にrequest_uriパラメータの値を登録することを要求するかどうか
op_policy_uri任意OpenID ProviderがRelying Partyに対して提供するユーザの情報がRelying Partyによってどの様に扱われることを求めているかを示すドキュメントのURL
op_tos_uri任意OpenID Providerの利用規約のURL


Discoveryエンドポイントを実装してみる

では実装してみます。非常に単純なエンドポイントなので/.well-known/openid-configurationに対するGETリクエストがあったら必要な情報をJSONで返却するだけです。

githubに公開しているソースのうち、この部分が該当します。

https://github.com/fujie/oidc-study-op/blob/main/endpoints/discovery.js



const router = require("express").Router();

// Discoveryエンドポイント
router.get("/openid-configuration", (req, res) => {
    const baseUrl = 'https://' + req.headers.host;
    const response_types = ["code"];
    const subject_types = ["public"];
    const alg_values = ["RS256"];
    const scopes = ["openid"];
    const auth_methods = ["client_secret_post", "client_secret_basic"];
    const claims =["sub", "name", "given_name", "family_name", "email", "email_verified", "iss", "aud", "exp", "exp", "iat"];
    res.json({
        issuer: baseUrl,
        authorization_endpoint: baseUrl + "/oauth2/authorize",
        token_endpoint: baseUrl + "/oauth2/token",
        userinfo_endpoint: baseUrl + "/userinfo",
        jwks_uri: baseUrl + "/jwks_uri",
        response_types_supported: response_types,
        subject_types_supported: subject_types,
        id_token_signing_alg_values_supported: alg_values,
        scopes_supported: scopes,
        token_endpoint_auth_methods_supported: auth_methods,
        claims_supported: claims
    });
});
module.exports = router;


node.js.+ express routerで実装しているので/.well-knownへのアクセスはこのJSへルーティングされる様にしています。

やっていることは単純で必要な情報をres.jsonで返却しているだけですね。


Relying PartyはOpenID Providerへリクエストをする際にこのエンドポイントにアクセスして最新の構成情報を取得して各種エンドポイントを利用する、という動きをする、ということですね。(パフォーマンスの観点でRelying Party側でメタデータをキャッシュをすることも多いと思います)

0 件のコメント: