Skip to content

使い方 — 動的クライアント登録

動的クライアント登録とは

最も素朴な構成では、RP を 1 つ統合するたびに、OP 運用者が client_id / client_secret / redirect URI / scope などを 設定 に手で書き足します。社内アプリ数個なら問題ありませんが、毎週新しい連携が増えるパブリックなエコシステムにはスケールしません。

動的クライアント登録 (DCR) は、RP が実行時に自分自身を登録できる JSON API です。RP がメタデータを POST すると、OP は新しい client_id と認証情報を返します。乱用を防ぐため、登録は Initial Access Token (IAT) で受け付け範囲を制限します。IAT は運用者が事前に発行するトークンで、許可するメタデータ・有効期限・single-use などの制約をかけられます。

このページで触れる仕様
  • RFC 7591 — Dynamic Client Registration Protocol
  • RFC 7592 — Dynamic Client Registration Management(読取 / 更新 / 削除)
  • RFC 8414 — Authorization Server Metadata(discovery)
  • RFC 8252 — OAuth 2.0 for Native Apps(後述のループバックリダイレクト規定)
  • OpenID Connect Core 1.0 — §2(auth_time / acr / default_max_age
用語の補足
  • Initial Access Token (IAT) — 運用者が仕様外の経路で発行する短寿命の Bearer トークン。OP は IAT 無しの POST /register を拒否します。任意の匿名呼び出しからクライアント生成を防ぐためです。
  • Registration Access Token (RAT) — 登録成功時の 201 応答に新しい client_id と一緒に含まれます。RP は registration_client_uri に対して RAT を使って RFC 7592 の読み取り / 更新 / 削除を実行します。

ソース: examples/41-dynamic-registration

アーキテクチャ

動的クライアント登録のシーケンス: 運用者が Initial Access Token を発行し仕様外の経路で新規 RP に渡すと、RP は POST /register で登録し、RFC 7592 で登録内容を読み取り・更新・削除する。運用者OP新規 RPIssueInitialAccessToken(ctx, spec)<iat>仕様外の経路で受け渡し <iat>POST /registerAuthorization: Bearer <iat>{ redirect_uris, …, client_name }201{ client_id, client_secret?,registration_access_token,registration_client_uri, … }GET /register/<client_id>Authorization: Bearer <rat>200 クライアントメタデータ全体PUT /register/<client_id> …200 更新済みメタデータDELETE /register/<client_id>204 本文なし1234567891011

設定

go
import (
  "github.com/libraz/go-oidc-provider/op"
)

provider, err := op.New(
  /* 必須オプション */
  op.WithDynamicRegistration(op.RegistrationOption{
    AllowedGrantTypes:    []string{"authorization_code", "refresh_token"},
    AllowedResponseTypes: []string{"code"},
  }),
)

// IAT を運用で発行。RP には仕様外の経路で渡す。
iat, err := provider.IssueInitialAccessToken(ctx, op.InitialAccessTokenSpec{
  TTL:     24 * time.Hour,
  MaxUses: 1,
})

op.WithDynamicRegistration は暗黙のうちに feature.DynamicRegistration を有効化し、/register をマウントして、discovery 文書に registration_endpoint を出力します。op.WithFeature(feature.DynamicRegistration) も同時に渡す必要はありません。重複指定は、登録ポリシーの所有箇所が曖昧にならないようコンストラクタで拒否されます。

オープン登録と既定 scope

RegistrationOption.Opentrue にすると、OP は Initial Access Token なしで POST /register を受け付けます — ネットワーク到達できる任意の呼び出し元がクライアントを生成できます。本ライブラリはこの帰結を、scope 省略時は空の scope セットで永続化 することで狭めています。そのクライアントは登録を更新するまで /authorize でいかなる scope も要求できません。

go
op.WithDynamicRegistration(op.RegistrationOption{
  Open:                          true,
  AllowedGrantTypes:             []string{"authorization_code", "refresh_token"},
  AllowedResponseTypes:          []string{"code"},
  OpenRegistrationDefaultScopes: []string{"openid"}, // scope 省略時の基準
})

OpenRegistrationDefaultScopes は明示的に設定した場合だけ有効です。各エントリは OP の scope カタログに登録済みでなければなりません(組み込みの OIDC 標準 scope 6 つに加えて WithScope(...) で追加したものを含む)。未知の値は op.New で拒否されます。IAT 経由の登録は変わらず — Initial Access Token を提示した場合は store.InitialAccessToken.AllowedScopes が優先します。

オープン登録の scope 既定は空です

scope を省略したオープンな POST には、組み込み側が OpenRegistrationDefaultScopes を設定しない限り既定 scope は付きません。登録直後のクライアントに openid などの基準 scope を要求させたい場合は、このオプションを明示してください。

認証コンテキスト系のクライアントメタデータ

/authorize の既定値と発行 id_tokenauth_time を制御するメタデータが 3 つあります。DCR 登録(RFC 7591)でも op.ClientSeed の静的シードでも受理され、リクエスト時に OP 側で強制されます。

フィールド効果仕様
default_max_age(nullable な整数)リクエストが max_age を省略した場合の既定値として適用されます。フィールドは保存から応答まで nullable のままなので、「未指定」と「明示的な 0(再認証必須)」が通信路上でもストア上でも区別され続けます。OIDC Core 1.0 §2 / Dynamic Client Registration §2
default_acr_valuesリクエストが acr_values を省略した場合の既定値として適用されます。op.WithACRPolicyMFA / ステップアップ)と組み合わせて AAL 階層へマップします。OIDC Core 1.0 §2 / Dynamic Client Registration §2
require_auth_timetrue のとき、発行される id_token には必ず auth_time が乗らなければなりません。OP が元の認証時刻を復元できない場合、値を捏造する代わりに server_error でトークン発行を失敗させます。OIDC Core 1.0 §2

なぜ auth_time 不在で server_error なのか

require_auth_time の違反は実運用ではめったに起こりません — OP がログインフローを自前で実行している限り auth_time は記録されます。捏造(例: iat で代替)してしまうと、ステップアップ保証を auth_time で監査している RP を気付かれずに壊してしまいます。構築時に拒否することで、欠落の原因が発生した地点で表面化させます。

譲れないセキュリティの最低ライン

Loopback の redirect_uris と DNS rebinding

application_type の既定は web です。Web クライアントが httpredirect_uri を登録できるのは host が IP リテラル 127.0.0.1 または [::1] のときだけで、文字列 localhost は既定で拒否します — RFC 8252 §8.3 の DNS-rebinding 窓を閉じるためです。localhost を正当に使う Web クライアントは op.WithAllowLocalhostLoopback() を明示します。安全側の既定からの逸脱が設定箇所に見える設計です。

ネイティブクライアント(application_type=native)は OIDC Registration §2 に従い、3 種類の loopback host(127.0.0.1 / [::1] / localhost)すべてを http で無条件に受け付けます。さらに claimed https、および RFC 8252 §7.1 の reverse-DNS custom URI scheme(例: com.example.app:/callback)も登録できます。. を含まない custom scheme はアプリ間で衝突しやすいため拒否します。

jsonc
// NG: web client の http://localhost は既定で拒否
{
  "application_type": "web",
  "redirect_uris": ["http://localhost:5173/callback"]
}

// OK: web client の loopback 開発は IP リテラルを使う
{
  "application_type": "web",
  "redirect_uris": ["http://127.0.0.1:5173/callback"]
}

// OK: native client では localhost loopback も許容される
{
  "application_type": "native",
  "redirect_uris": ["http://localhost:49152/callback"]
}

登録時に強制している内容

DCR は完全実装とは表記していませんが、対応しない差分は意図的な設計判断であって TODO ではありません。バリデータは POST /registerPUT /register/{client_id} のいずれでも、以下に違反するメタデータを拒否します:

  • application_type ごとの redirect_uris 形(上のワーニングを参照)。fragment 無し、絶対 URL のみ。
  • grant_typesresponse_types を OIDC Core §3 / OIDC Registration §2 の組み合わせ表に対してクロスチェック。整合しない組は invalid_client_metadata で拒否し、黙って自動修正することはありません。
  • jwksjwks_uri は同時指定不可。URI 系メタデータ(client_urilogo_uripolicy_uritos_urijwks_urisector_identifier_uriinitiate_login_uri)は絶対 URI、https、fragment 無しを要求。userinfo セグメント(https://user:pass@host/...)は拒否します。例外: request_uris は fragment を許容します。OIDC Core §6.2 が request file の base64url SHA-256 ハッシュを fragment として推奨しており、cache が内容変更を検出できるようにするためです。それ以外の形ルール(絶対 URI、https、host 必須、userinfo 不可)は通常通り適用されます。
  • backchannel_logout_urihttps 必須、fragment / userinfo 不可、host 必須。backchannel_logout_session_required=true と空の backchannel_logout_uri の組み合わせは invalid_client_metadata で拒否します — 配送先を持たないクライアントが sid 配送を有効化できないようにするためです。
  • sector_identifier_uri は登録時に GET で取得し、応答 JSON 配列に登録する redirect_uri がすべて含まれることを検証(OIDC Core §8.1)。取得は 5 秒のタイムアウトと 64 KiB の body サイズ上限で制限し、取得失敗または包含未達はいずれも invalid_client_metadata
  • subject_type=pairwisesector_identifier_uri が無い場合、redirect_uri の host はすべて同一でなければなりません。
  • request_object_signing_algRS256 / PS256 / ES256 / EdDSA に限定されます。

URI 系メタデータの典型的な境界は次の形です。

jsonc
// NG: jwks と jwks_uri の同時指定、userinfo、fragment は拒否
{
  "jwks": { "keys": [] },
  "jwks_uri": "https://client.example.com/jwks.json",
  "client_uri": "https://user:pass@client.example.com/app",
  "policy_uri": "https://client.example.com/policy#v1"
}

// OK: URI 系メタデータは https 絶対 URI、fragment / userinfo 無し
{
  "jwks_uri": "https://client.example.com/jwks.json",
  "client_uri": "https://client.example.com/app",
  "policy_uri": "https://client.example.com/policy"
}

// OK: request_uris だけは request file hash の fragment を許容
{
  "request_uris": [
    "https://client.example.com/request.jwt#sha256-abc123"
  ]
}

意図的な制約

full を名乗らない残差は、設計判断であって積み残しではありません。判断の根拠は 設計判断 ページに別エントリとして残しています — client_secret の非開示(#dj-20)、PUT 省略のセマンティクス(#dj-21)、sector_identifier_uri の fetch と native loopback ルール(#dj-22)。

  • GET /register/{id} では client_secret を再掲しない。 ストアは hash しか保持せず、平文は最初の POST /register と、後述する 2 つの PUT ケースだけで応答に乗ります。RFC 7591 §3.2.1 は読み取り応答での client_secret を OPTIONAL としており、非準拠ではありません。
  • PUT の省略は削除ではなくサーバ既定へのリセット。 PUT /register/{client_id}grant_typesresponse_typestoken_endpoint_auth_methodapplication_typesubject_typeid_token_signed_response_alg のいずれかを省略すると、そのフィールドは OP の既定値に戻ります。任意メタデータ(client_urilogo_uripolicy_uritos_uri、…)は空値になります。
  • PUT が client_secret を再掲するのは (a) none から confidential への auth method 昇格、(b) 明示的な rotation 要求のいずれか。 通常のメタデータ編集の応答には平文 secret は含まれません。
  • PUT の body にサーバ管理のフィールドを含めてはならない。 registration_access_tokenregistration_client_uriclient_secret_expires_atclient_id_issued_at を含めると 400 invalid_request。認証中のクライアントの client_secret と一致しない値を送っても 400 になります。
  • backchannel_logout_uribackchannel_logout_session_required は end-to-end でラウンドトリップします。 いずれも POST /register で永続化され、GET /register/{client_id} で返却、PUT /register/{client_id} で上書きできます。
  • software_statement(RFC 7591 §2.3)は非対応。 指定されたリクエストは invalid_software_statement で拒否します。federation / trust chain はスコープ外です。

読み取り / 更新 / 削除

201 レスポンスは registration_access_tokenregistration_client_uri を含みます。RP はこれらを使って RFC 7592 の操作を呼びます:

sh
# read
curl -H "Authorization: Bearer $RAT" $RCU

# update
curl -X PUT -H "Authorization: Bearer $RAT" -H "Content-Type: application/json" \
  -d '{"client_name":"New Name", ...}' $RCU

# delete
curl -X DELETE -H "Authorization: Bearer $RAT" $RCU

採用すべきとき

DCR が活きるのは:

  • 各テナントが自分の RP を持ち込み、設定変更の段階公開を避けたい multi-tenant SaaS。
  • チームが自分でクライアントクレデンシャルを取得できる内部 developer platform。

DCR が過剰で、不要な攻撃面になりやすいのは:

  • RP が 10 個、全部内部、全部既知のケース。op.WithStaticClients(...) の方がシンプルで可動部品も少なくて済みます。