RFC 9126 PAR で認可リクエストを保護する(Keycloak で試す)
Pushed Authorization Requests(PAR / RFC 9126)を Keycloak で試します。認可リクエストをバックチャネルで事前登録し、ブラウザや中継機器への露出を減らしながら、クライアント認証と早期検証を行う仕組みを確認します。
はじめに
OAuth 2.0 の認可コードフローでは、クライアントがブラウザを介して認可サーバーへ認可リクエストを送ります。
通常の認可リクエストでは、client_id、scope、redirect_uri、state、code_challenge などが URL のクエリに含まれます。
クライアントと認可サーバーが直接通信する経路をバックチャネル、ブラウザを介して通信する経路をフロントチャネルと呼びます。
この方式では、認可リクエストの内容がブラウザや中継機器を通過します。
また、認可サーバーがクライアントを認証し、redirect_uri や scope などを検証できるのは、ブラウザから認可リクエストを受け取った後です。
Pushed Authorization Requests(PAR) は、認可リクエストをバックチャネルで認可サーバーへ事前登録し、後続の認可リクエストで使用する request_uri を受け取る仕様です。
認可サーバーはユーザー操作を始める前に、認証情報を持つクライアントを認証し、認可リクエストを検証できます。
Keycloak を認可サーバーに設定し、通常の認可リクエストと PAR の通信フローを比較します。
RFC 9126 の要件を確認したうえで、成功例と失敗例を curl と mise で検証します。
最後に、PAR が保護する範囲と JAR(JWT-Secured Authorization Request) との責務の違いを整理します。
サンプルの Go Web アプリは、Terraform で作成する機密クライアント myapp(Client A)を使います。
/login は state、nonce、PKCE の code_verifier を生成して PAR を実行し、成功したときだけブラウザを認可エンドポイントへリダイレクトします。
Client B(myapp-binding)は、発行済み request_uri のクライアント束縛を確認する curl 検証専用です。
Financial-grade API(FAPI)のプロファイル全体、OAuth 2.0 のプライバシー問題全般、JAR における署名と暗号化の詳細は扱いません。
JAR の仕組みは、RFC 9101 の JAR を Keycloak で試した記事で扱っています。
成果物
https://github.com/kntks/blog-code/tree/main/2026/09/oauth-par
通常の認可リクエストと課題
認可コードフロー
RFC 6749 の Authorization Code Grant に OpenID Connect と PKCE(RFC 7636) を組み合わせた認可コードフローは、次のようになります。
この例では、クライアントが Go アプリケーション、認可サーバーが Keycloak、ユーザーエージェントがブラウザです。
通常の認可リクエストでは、パラメーターを URL のクエリに設定します。
http://localhost:8080/realms/myrealm/protocol/openid-connect/auth? response_type=code& client_id=myapp& redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fcallback& scope=openid& state=...& nonce=...& code_challenge=...& code_challenge_method=S256ブラウザを経由することで生じる課題
通常の認可リクエストでは、URL に含まれるパラメーターがブラウザの履歴やリバースプロキシのアクセスログに残ります。
TLS は通信経路上の盗聴を防ぎますが、URL を記録または転送するコンポーネントからパラメーターが見える可能性は残ります。
もう一つの課題は、認可サーバーがブラウザからリクエストを受け取るまで、認可リクエストを検証できないことです。
また、通常の認可エンドポイントではクライアント認証を行わないため、機密クライアントの認証は後続のトークンリクエストで行われます。
不正な redirect_uri や許可されていない scope を含むリクエストでも、最初にフロントチャネルへ送信されます。
| 課題 | 通常の認可リクエスト | PAR を使う場合 |
|---|---|---|
| URL への露出 | 認可パラメーターをクエリに含める | ブラウザに渡す値を client_id と request_uri に短縮できる |
| クライアント認証 | 認可エンドポイントでは通常行わない。機密クライアントは後続のトークンリクエストで認証する | PAR エンドポイントでトークンエンドポイントと同じ規則により認証する |
| リクエスト検証 | ブラウザから認可エンドポイントへ到達した後に行う | ユーザー操作を始める前に検証できる |
| URL の長さ | パラメーターが増えるほど長くなる | 認可エンドポイントへ送る URL を短くできる |
URL を短くできることは PAR の利点の一つです。
PAR は認可リクエストのペイロードをバックチャネルで送信するため、クライアント認証と早期検証が可能になります。
PAR の仕組み
PAR エンドポイントの役割
RFC 9126 は、認可リクエストのペイロードをクライアントから認可サーバーへ直接送信し、後続の認可リクエストで使う request_uri と交換する方法を定義しています。
クライアントが認可パラメーターを送る先は、認可エンドポイントではなく PAR エンドポイントです。
認可サーバーは認可リクエストを保持し、そのリクエストを参照するための有効期間が短い request_uri を返します。
PAR を使った認可コードフロー
この例でブラウザを経由する認可リクエストは、次の形になります。
http://localhost:8080/realms/myrealm/protocol/openid-connect/auth? client_id=myapp& request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3A...個々の認可パラメーターは URL のクエリに現れなくなります。
一方で、request_uri 自体はブラウザを経由するため、PAR を使っても認可フローに関するすべての情報がログに残らなくなるわけではありません。
Go アプリの /login は、PAR エンドポイントへ application/x-www-form-urlencoded 形式で POST し、Client A の client_id と client_secret を client_secret_basic(HTTP Basic 認証)で送ります。
レスポンスが 201 Created で、空でない request_uri と正の expires_in を含む場合だけ、認可エンドポイントへ client_id と request_uri だけを付けて 302 Found を返します。
PAR が失敗した場合、通常の認可リクエストへフォールバックせず、詳細をサーバーログに記録して、ブラウザには 502 Bad Gateway を返します。
コールバックでは state、nonce、PKCE の code_verifier を Cookie から取り出し、認可コードをトークンエンドポイントで交換します。
取得した ID Token は、JWKS で署名を検証し、issuer、Client A の audience、nonce、有効期限を確認してからセッション Cookie を設定します。
RFC 9126 の要件
PAR エンドポイントへのリクエスト
PAR エンドポイントは、UTF-8 の application/x-www-form-urlencoded 形式を使う HTTP POST を受け付けます。
本番環境のエンドポイントは https スキームでなければなりません。RFC 9126 Section 2
PAR エンドポイントには、通常の認可エンドポイントで使えるパラメーターと、PKCE や OpenID Connect などの拡張パラメーターを送信できます。
ただし、認可サーバーが返す request_uri を PAR リクエストへ含めてはなりません。
PAR エンドポイントのクライアント認証には、トークンエンドポイントと同じ規則が適用されます。
クライアントは、登録された認証方式を使って認証します。
client_id は PAR リクエストにも必須です。RFC 9126 Section 2.1
認可サーバーによる検証
認可サーバーは、PAR リクエストに対して次の確認を行います。
- トークンエンドポイントと同じ方法でクライアントを認証する
- リクエストに
request_uriが含まれていた場合は拒否する - 通常の認可リクエストと同様に、
redirect_uri、scopeなどを検証する
この処理により、不正なリクエストをユーザー操作の前に拒否できます。
認可サーバーが PAR の時点で実施した検証を認可エンドポイントで省略するには、リクエストとポリシーに変更がなく、検証結果が変わらないことを確認できなければなりません。
成功レスポンスと request_uri
検証に成功すると、認可サーバーは HTTP 201 Created と JSON レスポンスを返します。RFC 9126 Section 2.2
| 項目 | RFC 9126 の要件 |
|---|---|
| HTTP メソッド | POST |
| Content-Type | UTF-8 の application/x-www-form-urlencoded |
| エンドポイント | 本番環境では https が必須 |
client_id | PAR リクエストでも必須 |
| クライアント認証 | トークンエンドポイントと同じ規則を適用 |
| 事前検証 | 通常の認可リクエストと同様に検証 |
| 成功ステータス | 201 Created |
| 成功レスポンス | request_uri と expires_in |
| 推測耐性 | request_uri に暗号学的に推測困難な部分を含める |
| クライアント束縛 | 発行した request_uri を投稿元クライアントへ束縛する |
| 有効期限 | 期限切れの request_uri を拒否する |
| 利用回数 | クライアントは一度だけ使用する。認可サーバーも原則として一回利用として扱う |
request_uri の形式と有効期間は認可サーバーが決めます。
RFC 9126 は一般的な有効期間の例として 5 秒から 600 秒を挙げていますが、特定の値を要求していません。
クライアントは request_uri を一度だけ使用しなければなりません。
認可サーバーは request_uri を一回利用として扱うことが推奨されていますが、ブラウザの再読み込みによる重複リクエストを許容できます。RFC 9126 Section 4
PAR の必須化
認可サーバーは、サーバー全体またはクライアント単位で PAR を必須にできます。
PAR が必須の場合、PAR エンドポイントから発行された request_uri を持たない認可リクエストは invalid_request で拒否されます。
| メタデータ | 意味 |
|---|---|
pushed_authorization_request_endpoint | PAR リクエストを送信するエンドポイント |
require_pushed_authorization_requests | PAR を唯一の認可リクエスト開始方法とするかどうか。省略時は false |
これらのメタデータは RFC 9126 Sections 5–6 で定義されています。
Keycloak で PAR を試す
検証環境
検証環境は、ブラウザ、Go アプリケーション、Keycloak で構成します。
基本フローを試すクライアントに加え、request_uri のクライアント束縛を確認するための別クライアントを使用します。
前提条件
- Docker と Docker Compose が使用できる
- mise が使用できる
mise installで、このリポジトリのmise.tomlに定義されたterraform、go、gopls、air、jqを導入するhttp://localhost:8080とhttp://localhost:8081にブラウザでアクセスできる
バージョン
| 項目 | バージョン |
|---|---|
| macOS | 26.6.2 |
| Keycloak | 26.7.3 |
| Go | 1.27.1 |
| gopls | 0.23.0 |
| air | 1.67.4 |
| Terraform | 1.16.1 |
| jq | 1.8.2 |
| Terraform Provider(Keycloak) | >= 5.6.0 |
構築手順
Keycloak を起動します。
cd blog-code/oauth-pardocker compose up -dツールは OS へ個別にインストールせず、mise.toml の定義から導入します。
mise installTerraform で Realm、User、Client を作成します。
terraform -chdir=terraform initterraform -chdir=terraform planterraform -chdir=terraform applyサンプルアプリを起動します。
air検証環境はローカルのため http を使用しますが、RFC 9126 が定める PAR エンドポイントは https が必須です。
Keycloak の起動と Terraform の apply が完了した後、検証タスクを個別に実行します。
PAR エンドポイントを確認する
Keycloak の OpenID Provider Configuration から、pushed_authorization_request_endpoint と require_pushed_authorization_requests を取得します。
mise の検証タスクを使うと、レスポンス本文と自動判定を同時に確認できます。
mise run kc:par:discover$ curl -s http://localhost:8080/realms/myrealm/.well-known/openid-configuration | jq '{ "pushed_authorization_request_endpoint": .pushed_authorization_request_endpoint, "require_pushed_authorization_requests": .require_pushed_authorization_requests}'{ "pushed_authorization_request_endpoint": "http://localhost:8080/realms/myrealm/protocol/openid-connect/ext/par/request", "require_pushed_authorization_requests": false}pushed_authorization_request_endpoint が存在すれば、クライアントは認可サーバーが PAR を利用できると判断できます。
Keycloak 26.7.3 では、この構成の Discovery に含まれる require_pushed_authorization_requests は false です。
この値は Client A の PAR 必須設定とは別であり、Client A を個別に必須にしても変わりません。
したがって、Discovery の値から Client A の設定を判断することはできません。
Client A の PAR 必須化は、extra_config で設定します。
認可リクエストを事前登録する
認可パラメーターを PAR エンドポイントへ送信します。
この例では HTTP Basic 認証によってクライアントを認証します。
CLIENT_ID=myappCLIENT_SECRET=your-client-secretREDIRECT_URI=http://localhost:8081/callbackISSUER_URL=http://localhost:8080/realms/myrealmPAR_ENDPOINT=$(curl -sS "$ISSUER_URL/.well-known/openid-configuration" \ | jq -er .pushed_authorization_request_endpoint)BASIC_CLIENT_ID=$(jq -nr --arg value "$CLIENT_ID" '$value | @uri')BASIC_CLIENT_SECRET=$(jq -nr --arg value "$CLIENT_SECRET" '$value | @uri')
STATE=$(openssl rand -hex 32)NONCE=$(openssl rand -hex 32)CODE_VERIFIER=$(openssl rand -base64 64 | tr -d '=+/' | head -c 64)CODE_CHALLENGE=$(printf %s "$CODE_VERIFIER" \ | openssl dgst -sha256 -binary \ | openssl base64 -A \ | tr '+/' '-_' \ | tr -d '=')
PAR_RESPONSE=$(curl -s -u "$BASIC_CLIENT_ID:$BASIC_CLIENT_SECRET" \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "response_type=code" \ --data-urlencode "client_id=$CLIENT_ID" \ --data-urlencode "redirect_uri=$REDIRECT_URI" \ --data-urlencode "scope=openid" \ --data-urlencode "state=$STATE" \ --data-urlencode "nonce=$NONCE" \ --data-urlencode "code_challenge=$CODE_CHALLENGE" \ --data-urlencode "code_challenge_method=S256" \ "$PAR_ENDPOINT")
echo "$PAR_RESPONSE" | jqclient_secret_basic では、RFC 6749 に従い Client ID と Client Secret をそれぞれ application/x-www-form-urlencoded 形式へ変換してから HTTP Basic 認証へ渡します。
Base64 で生成した値には + や / が現れることがあり、このサンプルの固定 Client Secret にも + が含まれます。
未変換のまま curl -u "$CLIENT_ID:$CLIENT_SECRET" とすると Keycloak のクライアント認証に失敗するため、Go アプリと mise タスクも同じ変換を行います。
成功すると、Keycloak は request_uri と expires_in を返します。
RFC 9126 上の成功ステータスは 201 Created です。
{ "request_uri": "urn:ietf:params:oauth:request_uri:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "expires_in": 300}この環境では、Terraform の Realm 属性 parRequestUriLifespan = "300" により expires_in が 300 になります。
RFC 9126 自体はこの値を固定していません。
request_uri で認可を開始する
レスポンスから request_uri を取り出します。
REQUEST_URI=$(echo "$PAR_RESPONSE" | jq -r .request_uri)認可エンドポイントへ送るのは client_id と request_uri です。
http://localhost:8080/realms/myrealm/protocol/openid-connect/auth? client_id=myapp& request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Axxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxstate、nonce、code_challenge などは、PAR エンドポイントへ事前に送信したリクエストから復元されます。
これを mise のタスクとして実行すると以下のような結果になります。
$ mise run kc:par:request説明:Client A で Basic 認証付き PAR を実行し、生レスポンスを表示する。HTTP 201、request_uri、expires_in=300 を確認し、認可 URL(client_id + request_uri)を表示する
PAR HTTP status: 201Raw PAR response:{ "request_uri": "urn:ietf:params:oauth:request_uri:77b43ada-50ab-bab3-8ed1-fa62a9e78980", "expires_in": 300}
Authorization URL: http://localhost:8080/realms/myrealm/protocol/openid-connect/auth?client_id=myapp&request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3A77b43ada-50ab-bab3-8ed1-fa62a9e78980不正な redirect_uri を事前に拒否する
PAR エンドポイントへ、クライアントに登録されていない redirect_uri を送ります。
curl -i -u "$BASIC_CLIENT_ID:$BASIC_CLIENT_SECRET" \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "response_type=code" \ -d "client_id=$CLIENT_ID" \ -d "redirect_uri=https://invalid.example/callback" \ -d "scope=openid" \ "$PAR_ENDPOINT"ここでは、ログインと同意の画面へ進む前にリクエストが拒否されることを確認します。
RFC 9126 では、不正または不一致の redirect_uri に対して invalid_request を既定のエラーコードとして使用できます。RFC 9126 Section 2.3
これを mise のタスクとして実行すると以下のような結果になります。
$ mise run kc:par:reject-redirect説明:未登録の redirect_uri を含む Pushed Authorization Request を PAR エンドポイントへ送る。生レスポンスを表示し、HTTP 400 と OAuth エラーの手掛かりを確認する
Unregistered redirect PAR HTTP status: 400Raw response:{"error":"invalid_request","error_description":"Invalid parameter: redirect_uri"}PAR を必須にする
Terraform の keycloak_openid_client.main.extra_config["require.pushed.authorization.requests"] = "true" で、Client A(myapp)だけに Pushed authorization request required を設定します。
Client B(myapp-binding)の extra_config は空で、PAR 必須ではありません。
Realm レベルで設定しているのは parRequestUriLifespan = "300" だけです。
設定後、PAR を使わずに認可エンドポイントへ通常の認可パラメーターを送ります。
http://localhost:8080/realms/myrealm/protocol/openid-connect/auth? response_type=code& client_id=myapp& redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fcallback& scope=openidKeycloak 26.7.3 はこのリクエストを、error=invalid_request と error_description=Pushed+Authorization+Request+is+only+allowed. を付けた登録済みコールバックへの HTTP 302 リダイレクトとして拒否します。
PAR エンドポイントで発行された request_uri を使うリクエストだけが受け付けられることを確認します。
この挙動は Client A 単位の設定によるものです。
Discovery に表示される require_pushed_authorization_requests: false は変わらず、Client A が個別に PAR 必須であることと矛盾しません。
これを mise のタスクとして実行すると以下のような結果になります。
$ mise run kc:par:reject-nonpar説明:Client A でrequest_uri なしの通常認可リクエストを送る。生レスポンスヘッダーを表示し、HTTP 302 と Locationの error=invalid_request、PAR 必須を示す説明を確認する
Non-PAR authorization HTTP status: 302Raw response headers:HTTP/1.1 302 FoundCache-Control: no-store, must-revalidate, max-age=0Location: http://localhost:8081/callback?error=invalid_request&error_description=Pushed+Authorization+Request+is+only+allowed.&state=7f1195ff935139f4e8ef26f3aefde893&iss=http%3A%2F%2Flocalhost%3A8080%2Frealms%2FmyrealmReferrer-Policy: no-referrerStrict-Transport-Security: max-age=31536000; includeSubDomainsX-Content-Type-Options: nosniffX-Robots-Tag: nonecontent-length: 0
Raw response body:request_uri のクライアント束縛を確認する
RFC 9126 は、request_uri を PAR リクエストの投稿元クライアントへ束縛することを認可サーバーへ要求しています。
Client A が取得した request_uri と、Client B の client_id を組み合わせて認可エンドポイントへ送ります。
http://localhost:8080/realms/myrealm/protocol/openid-connect/auth? client_id=myapp-binding& request_uri=<Client A が取得した request_uri>この組み合わせが拒否されれば、別のクライアントが取得済みの request_uri を流用できないことを確認できます。
期限切れと再利用への対応では、Keycloak の実装固有の挙動と RFC 9126 の要件を区別します。
RFC 9126 は、期限切れの値を拒否すること、クライアントが一度だけ使用すること、認可サーバーが原則として一回利用として扱うことを定めています。
これを mise のタスクとして実行すると以下のような結果になります。
$ mise run kc:par:reject-binding説明:Client A が取得した request_uri を Client B で使う。両方の生レスポンスを表示し、HTTP 400 とクライアント束縛エラーを確認する
Client A PAR HTTP status: 201Raw client A PAR response:{"request_uri":"urn:ietf:params:oauth:request_uri:601aad6f-67af-7272-63b7-91a0c9e9c641","expires_in":300}Client B using client A request_uri HTTP status: 400Raw binding response:<!DOCTYPE html><html class="login-pf" lang="en">
<head> # 省略</head>
<body class="" data-page-id="login-error"> # 省略</body></html>
Checked client B=myapp-binding against client A request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3A601aad6f-67af-7272-63b7-91a0c9e9c641mise による検証タスク
検証タスクは、環境準備や Terraform の apply とは分離しています。
Keycloak の起動と terraform apply が完了した後、各タスクを個別に実行できます。
各タスクは生レスポンスを表示し、終了ステータスと内容も自動判定します。
| タスク | 確認内容と自動判定 |
|---|---|
mise run kc:par:discover | Discovery の生 JSON を表示し、HTTP 200、PAR エンドポイント、認可エンドポイントの存在と require_pushed_authorization_requests=false を確認する |
mise run kc:par:request | Client A で Basic 認証付き PAR を実行し、生レスポンスを表示する。HTTP 201、request_uri、expires_in=300 を確認し、認可 URL(client_id + request_uri)を表示する |
mise run kc:par:reject-redirect | 未登録の redirect_uri を含む Pushed Authorization Request を PAR エンドポイントへ送る。生レスポンスを表示し、HTTP 400 と OAuth エラーの手掛かりを確認する |
mise run kc:par:reject-nonpar | Client A で request_uri なしの通常認可リクエストを送る。生レスポンスヘッダーを表示し、HTTP 302 と Location の error=invalid_request、PAR 必須を示す説明を確認する |
mise run kc:par:reject-binding | Client A が取得した request_uri を Client B で使う。両方の生レスポンスを表示し、HTTP 400 とクライアント束縛エラーを確認する |
mise run kc:par:verify | 上記 5 タスクを discover、request、reject-redirect、reject-nonpar、reject-binding の順に実行する |
正常な PAR の検証では、Client A(myapp)と Go アプリが共有する Client Secret を使います。
Client B(myapp-binding)は request_uri の束縛検証にだけ使い、PAR を必須にしていません。
PAR が保護するものと限界
PAR が変えるのは、認可リクエストの送信経路と処理のタイミングです。
PAR によって得られる保護と、PAR だけでは解決しない問題を整理します。
| 観点 | PAR による効果 | 限界と追加対策 |
|---|---|---|
| 認可パラメーターの露出 | 個々のパラメーターをバックチャネルで送る | request_uri 自体はブラウザを経由する |
| クライアント認証 | 認証可能なクライアントをユーザー操作前に認証できる | クライアント種別と登録済み認証方式に依存する |
| 早期検証 | redirect_uri や scope などを事前検証できる | PAR 時点で実行できない検証は認可時に必要 |
| 推測とリプレイ | 推測困難で短命な参照を使い、クライアントに束縛する | 認可サーバーによる一回利用の扱いが重要 |
| プライバシー | URL クエリによる意図しない情報開示を減らせる | OAuth 2.0 全体のプライバシー問題を解決するものではない |
RFC 9126 は、request_uri の差し替えに備えて、PKCE、一意な state、OpenID Connect の nonce のいずれかを使用することを推奨しています。RFC 9126 Section 7.5
今回の Go アプリは、これら三つを使用します。
認可リクエストを PAR エンドポイントへ登録してから、ブラウザが認可エンドポイントへ到達するまでにクライアントポリシーが変わる可能性もあります。
認可サーバーは、認可エンドポイントでも現在のポリシーに基づいてリクエストを評価します。
PAR と JAR の責務分担
今回のリポジトリには、JAR の Request Object を生成、署名、検証する実装はありません。
mise の検証タスクも PAR 単体だけを対象にします。
PAR と JAR は、認可リクエストの異なる側面を扱います。
| 仕様 | 主な役割 | ブラウザへ渡すもの |
|---|---|---|
| PAR | 認可リクエストをバックチャネルで事前登録し、認証と早期検証を行う | client_id と認可サーバーが発行した request_uri |
| JAR | 認可リクエストを Request Object にまとめ、JWS で完全性と送信元認証を保護する | request、または Request Object を参照する request_uri |
JAR の JWE を使う場合は Request Object の機密性も保護できます。
PAR は署名や暗号化そのものを提供する仕様ではないため、JAR の代替ではありません。
両者は組み合わせて利用できます。
PAR エンドポイントへ通常の認可パラメーターの代わりに JAR の Request Object を送信し、認可サーバーが発行した request_uri を後続の認可リクエストで使います。
# 概念例: REQUEST_OBJECT の生成、署名、検証は今回の実装に含まれません。CLIENT_ID=myappCLIENT_SECRET=your-client-secretREQUEST_OBJECT=eyJhbGci...ISSUER_URL=http://localhost:8080/realms/myrealmPAR_ENDPOINT=$(curl -sS "$ISSUER_URL/.well-known/openid-configuration" \ | jq -er .pushed_authorization_request_endpoint)BASIC_CLIENT_ID=$(jq -nr --arg value "$CLIENT_ID" '$value | @uri')BASIC_CLIENT_SECRET=$(jq -nr --arg value "$CLIENT_SECRET" '$value | @uri')
PAR_RESPONSE=$(curl -s -u "$BASIC_CLIENT_ID:$BASIC_CLIENT_SECRET" \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "client_id=$CLIENT_ID" \ --data-urlencode "request=$REQUEST_OBJECT" \ "$PAR_ENDPOINT")
REQUEST_URI=$(echo "$PAR_RESPONSE" | jq -r .request_uri)この構成では、PAR によるバックチャネル送信、クライアント認証、早期検証と、JAR による Request Object の完全性、送信元認証を組み合わせます。
JAR の by-reference 方式と PAR は、同じ request_uri という名前を使いますが、参照の作り方が異なります。
| 方式 | request_uri の作り方と取得経路 |
|---|---|
| JAR by-reference | クライアントが Request Object を参照する URI を用意し、認可サーバーが取得する |
| PAR | クライアントが認可リクエストを POST し、認可サーバーが管理する request_uri を発行する |
まとめ
PAR は、認可リクエストを認可サーバーへ事前登録し、後続のフロントチャネルでは request_uri で参照する仕組みです。
- 個々の認可パラメーターをバックチャネルで送り、ブラウザ上の露出を減らす
- 認証可能なクライアントを認証し、
redirect_uriやscopeなどをユーザー操作前に検証できる - 認可サーバーは
request_uriを短命で推測困難な値として発行し、投稿元クライアントに束縛する - Keycloak では PAR を必須化し、通常の認可リクエストを拒否できる
- PAR は送信経路と事前処理を扱い、JAR は Request Object の完全性と送信元認証を扱う
PAR は、認可リクエストに含まれる情報を一切見えなくする仕組みではありません。
request_uri の有効期限、一回利用、クライアント束縛、PKCE、state、nonce を組み合わせて運用します。