> ## Documentation Index
> Fetch the complete documentation index at: https://developers.authlete.com/llms.txt
> Use this file to discover all available pages before exploring further.

# グラント管理 (Grant Management for OAuth 2.0)

> Grant Management for OAuth 2.0 仕様および Authlete の実装の説明です。

# はじめに

**[Grant Management for OAuth 2.0](https://openid.net/specs/fapi-grant-management.html)** (グラント管理) は、クライアントアプリケーションに付与された権限を管理するための技術仕様で、既存の標準仕様よりも細かい粒度での管理を可能にします。

**[英国オープンバンキング](https://www.openbanking.org.uk/)**、**[オーストラリア消費者データ権](https://www.cdr.gov.au/)**、\*\*[ブラジルオープンバンキング](https://openbankingbrasil.org.br/)\*\*等のエコシステムで採用され、高度 API セキュリティのデファクトスタンダードとなった **Financial-grade API 1.0**、その後継仕様である **Financial-grade API 2.0** の一部と位置付けられていることが、この仕様に注目すべき理由となっています。また、グラント管理仕様は、インターネット上に世界規模の高信頼性デジタルアイデンティティネットワークを構築するためのプロジェクトである **GAIN** (**Global Assured Identity Network**) の[ホワイトペーパー](https://gainforum.org/GAINWhitePaper.pdf "GAIN DIGITAL TRUST")でも言及されています。

この記事は 2021 年 9 月にリリースされた実装者向けドラフト初版 ([アナウンス](https://openid.net/2021/09/21/implementers-draft-of-fapi-grant-management-approved/ "Implementer's Draft of FAPI Grant Management Approved")) をもとに仕様の説明をおこないます。しかしながら、他と同様、初版ドラフトは完璧ではありません。遅かれ早かれ実装者が必要とするであろう技術詳細が欠けており、異なる解釈を招く余地があります。しかし、初版ドラフトの目的が実装者に作業開始を促すことにある点を考慮すれば、不完全なことはむしろ自然とも言えます。

実装者からのフィードバックとして仕様改善に資することを期待し、本記事では仕様自体の説明に加えて Authlete が仕様をどのように解釈し実装したのかについても説明します。

# 仕様

## グラントの概念

OAuth 2.0 では、ユーザーはクライアントアプリケーションに権限を与えます。結果として、認可サーバーからクライアントアプリケーションにアクセストークンが発行されます。

アクセストークン発行処理は繰り返されるかもしれません。二番目以降の処理でクライアントが要求する権限は最初の処理で要求されたものとは異なるかもしれません。このようにして、クライアントアプリケーションに与えられる権限は累積していきます。

グラント管理仕様では累積した権限を**グラント**と呼び、グラントに関する問い合わせ、グラントの累積および失効の手段を定義します。

<img src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/protocols-and-flows/advanced-flows/grant.ja.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=3cd374355abd2ccb6a9fea5aba1e192a" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/grant.ja.png" />

## リクエストパラメーター／レスポンスパラメーター

グラントを管理するため、各グラントには**グラント ID** が割り当てられます。グラント ID はアクセストークンとともにトークンエンドポイントから発行されます。認可サーバーにグラント ID の発行を要求するには、新しい認可リクエストパラメーターである `grant_management_action` に `create` という値を指定し、認可リクエストに含めます。発行されたグラント ID は、新しいトークンレスポンスパラメーターである `grant_id` の値としてトークンレスポンスに含まれます。

<img src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/protocols-and-flows/advanced-flows/grant_management_action-create.ja.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=a21128157f13f4a3115503b81f6daee0" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/grant_management_action-create.ja.png" />

権限を累積させるには、続く認可リクエストに `grant_management_action=merge` および新しい認可リクエストパラメーター `grant_id` を含めます。`grant_id` の値には発行済みのグラント ID を指定します。

<img src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/protocols-and-flows/advanced-flows/grant_management_action-merge.ja.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=e822045f038f742e31f7cdd94355bf2d" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/grant_management_action-merge.ja.png" />

<Note>
  [FAPI PR 344](https://bitbucket.org/openid/fapi/pull-requests/344) により、`update` は `merge` へと名称変更されました。この変更は Grant Management for OAuth 2.0 の実装者向け草稿第二版から有効になります。
</Note>

`create` と `merge` に加え、`grant_management_action` リクエストパラメーターの値として `replace` も定義されています。`replace` アクションは、指定したグラント ID に紐付く権限を、`grant_management_action=replace` を含むリクエストで付与された権限だけで置き換えます。以前の権限は取り消されます。

<img src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/protocols-and-flows/advanced-flows/grant_management_action-replace.ja.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=67261b5b95ba41a514c9f77655d4ba8f" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/grant_management_action-replace.ja.png" />

<Note>
  *"[FAPI ISSUE 454: Expected behavior for grant\_management\_action=replace](https://bitbucket.org/openid/fapi/issues/454)"* に`grant_management_action=replace` に関する議論があります。
</Note>

`grant_management_action` リクエストパラメーターの値が `merge` または `replace` の場合、`grant_id` リクエストパラメーターは必須です。

<Note>
  **実装者のメモ**

  (1) `grant_id` が与えられているが `grant_management_action` が与えられていない場合、また、(2) `grant_id` が与えられているが `grant_management_action` の値が `create` ではない場合について、初版ドラフトは言及していません。この件を議論するため、*"[FAPI ISSUE 447: grant\_id is given but grant\_management\_action is not](https://bitbucket.org/openid/fapi/issues/447)"* を作成しました。Authlete の現在の実装は、これらの場合はリクエストを拒否します。

  **追記**

  上記のリクエストを認可サーバーの実装が拒否することを明示的に要求するため、[FAPI PR 290](https://bitbucket.org/openid/fapi/pull-requests/290) が作成されました。
</Note>

`grant_management_action` および `grant_id` リクエストパラメーターについて、仕様は次のように述べています。

<blockquote>
  These parameters can be used with any request serving as authorization request, e.g. it may be used with CIBA requests.

  *(これらのパラメーターは、認可リクエストとして動作するものであればどんなリクエストでも使えます。例えば CIBA リクエストで使ってもかまいません。)*
</blockquote>

具体的には、仕様ではバックチャネル認証エンドポイント ([CIBA Core](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html) / [Section 7](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html#rfc.section.7)) がこれらのパラメーターを認識することを期待しています。

<Note>
  **実装者のメモ**

  仕様では明示的に言及されてはいませんが、デバイス認可リクエスト ([RFC 8628](https://www.rfc-editor.org/rfc/rfc8628.html) / [Section 3.1](https://www.rfc-editor.org/rfc/rfc8628.html#section-3.1)) も該当すると考えてよいでしょう。Authlete の現在の実装は、認可エンドポイント ([RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html)) に加えて、バックチャネル認証エンドポイント ([CIBA](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)) およびデバイス認可エンドポイント ([RFC 8628](https://www.rfc-editor.org/rfc/rfc8628.html)) で、グラント管理関連のリクエストパラメーターを認識します。
</Note>

## グラント管理エンドポイント

グラント管理仕様では、**グラント管理エンドポイント**と呼ばれる新しいエンドポイントを定義しています。このエンドポイントにより、グラントに関する問い合わせとグラントの失効が可能となります。エンドポイントにアクセスするには、事前に発行されたアクセストークンが必要となります。

### 問い合わせ

| **問い合わせ** |              |                                                                                                                       |
| :-------- | :----------- | :-------------------------------------------------------------------------------------------------------------------- |
| リクエスト     | HTTP メソッド    | `GET`                                                                                                                 |
|           | URL          | `{エンドポイント}/{グラントID}`                                                                                                  |
|           | 保護           | `grant_management_query` スコープを持つアクセストークン                                                                              |
| レスポンス     | ステータスコード     | 200 OK                                                                                                                |
|           | Content-Type | `application/json`                                                                                                    |
|           | フォーマット       | "[6.4. Query Status of a Grant](https://openid.net/specs/fapi-grant-management.html#name-query-status-of-a-grant)" 参照 |

次のものは、グラント管理仕様の　"[6.4. Query Status of a Grant](https://openid.net/specs/fapi-grant-management.html#name-query-status-of-a-grant)" から抜粋したレスポンス例です。

<img src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/protocols-and-flows/advanced-flows/grant-status.ja.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=cc9114e7c7035fedc304ddb4c1a770f8" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/grant-status.ja.png" />

レスポンスボディーの JSON はグラントの権限を示しています。トップレベルのプロパティーとして `"scopes"`, `"claims"`, `"authorization_details"` が含まれています。

#### scopes

`"scopes"` の値は、スコープとリソースの組（以降スコープリソースクラスター）の配列です。各クラスターは `"scope"` と `"resource"` で構成されます。`"scope"` の値は空白区切りで並べたスコープ群です。`"resource"` の値はリソースの配列です。スコープ群とリソース群の値は `scope` リクエストパラメーター ([RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html) / [Section 3.3](https://www.rfc-editor.org/rfc/rfc6749.html#section-3.3)) と `resource` リクエストパラメーター ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html) / [Section 2](https://www.rfc-editor.org/rfc/rfc8707.html#name-resource-parameter)) で指定されたものです。

`"scopes"` の構造から、`merge` 操作の繰り返しにより権限が累積してもスコープリソースクラスター群は分けて管理すべきということに実装者は気が付きます。もしも `merge` 操作でクラスター群の内容が混ざってしまうと、スコープ群が意図しないリソース群と組み合わされてしまうかもしれません。下図内の右下の JSON は、クラスター群の内容を混ぜてしまうと `change` スコープが `https://payment.example.com` リソースに関連付けられてしまうことを示しており、これは、金銭の窃取などの重大なセキュリティ事件を引き起こしかねません。

<img src="https://mintcdn.com/authlete/oZd1VUaYFwHO2MBx/protocols-and-flows/advanced-flows/scope-resource-cluster.ja.png?fit=max&auto=format&n=oZd1VUaYFwHO2MBx&q=85&s=1eb525460a1395cdef1695c8fb00e9ff" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/scope-resource-cluster.ja.png" />

#### claims

`"claims"` の値は、ユーザーがクライアントアプリケーションに知ることを許可した「クレーム」の配列です。簡単に言うと、ここでの「クレーム」とはユーザーの属性のことです。`family_name` や `phone_number` などのユーザー属性のいくつかは、[OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) の [Section 5.1](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) で標準クレームとして定義されています。

クライアントアプリケーションは、特別なスコープ (`profile`、`email`、`address`、`phone`) を `scope` リクエストパラメーターに含めることにより ([OIDC Core](https://openid.net/specs/openid-connect-core-1_0.html) / [Section 5.4](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims))、または `claims` リクエストパラメーターを使うことにより ([OIDC Core](https://openid.net/specs/openid-connect-core-1_0.html) / [Section 5.5](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter))、クレームを要求することができます。

<img src="https://mintcdn.com/authlete/oZd1VUaYFwHO2MBx/protocols-and-flows/advanced-flows/requesting-claims.ja.png?fit=max&auto=format&n=oZd1VUaYFwHO2MBx&q=85&s=ec3c646173a3d1e7688306d898c01583" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/requesting-claims.ja.png" />

#### authorization\_details

`"authorization_details"` の値は認可に関する詳細情報の配列です。それらの情報は、"**[OAuth 2.0 Rich Authorization Requests](https://datatracker.ietf.org/doc/draft-ietf-oauth-rar/)**" (**RAR**) で定義される `authorization_details` リクエストパラメーターで指定されたものです。

RAR が開発される前は、認可リクエストに詳細情報を含めるために認可サーバーは非標準リクエストパラメーターを導入するか、または `scope` リクエストパラメーターを非標準の方法で利用しなければなりませんでした。後者の方法は「parameterized scope」や「dynamic scope」と呼ばれ、動的な値をスコープ文字列に埋め込みます。例えば、[Open Banking Brasil Financial-grade API Security Profile 1.0](https://openbanking-brasil.github.io/specs-seguranca/open-banking-brasil-financial-api-1_ID3.html) は「動的同意スコープ」([Section 7.1.2](https://openbanking-brasil.github.io/specs-seguranca/open-banking-brasil-financial-api-1_ID3.html#name-dynamic-consent-scope-defin)) を定義しています。そのフォーマットは `consent:{ConsentID}` となっており、`consent:` が固定文字列のプレフィックス、`{ConsentID}` が動的部分です (例: `consent:urn:bancoex:C1DD33123`)。

動的スコープの問題は、標準化されていないこと、および、それ自身に起因する特性として書式が多岐に渡ることにより、実装間の互換性に全く期待できないことです。RAR はその問題を解決するために開発され、Financial-grade API 2.0 の一部として位置付けられています。

<img src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/protocols-and-flows/advanced-flows/authorization_details.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=4f204895d3b0011b1ae9be35ca61463b" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/authorization_details.png" />

### 失効

| **失効** |           |                                           |
| :----- | :-------- | :---------------------------------------- |
| リクエスト  | HTTP メソッド | `DELETE`                                  |
|        | URL       | `{エンドポイント}/{グラントID}`                      |
|        | 保護        | `grant_management_revoke` スコープを持つアクセストークン |
| レスポンス  | ステータスコード  | 204 No Content                            |

グラント管理仕様は、失効するグラントに紐付く全てのリフレッシュトークンも失効しなければならず (MUST)、アクセストークンも失効すべき (SHOULD) と述べています。

<Note>
  **実装者のメモ**

  アクセストークンとリフレッシュトークンの実装は、認可サーバーの実装間で異なります。特に、PKI の [CRL](https://en.wikipedia.org/wiki/Certificate_revocation_list) (Certificate Revocation List) または [OCSP](https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol) (Online Certificate Status Protocol) 相当の仕組みを運用しない限り、**JWT アクセストークンをはじめとする内包型アクセストークンは失効できない**ことに注意してください。詳細は『[OAuth アクセストークンの実装に関する考察](https://qiita.com/TakahikoKawasaki/items/970548727761f9e02bcd)』を参照してください。

  Authlete の現在の実装では、失効するグラントに紐付く全てのリフレッシュトークンとアクセストークンが失効します。
</Note>

## サーバーメタデータ

グラント管理仕様は次のサーバーメタデータを追加します。

| メタデータ                                | 説明                                                                                  |
| :----------------------------------- | :---------------------------------------------------------------------------------- |
| `grant_management_actions_supported` | 認可サーバーがサポートするグラント管理アクション群。取りうる値は `create`, `query` `replace`, `revoke` および `merge`. |
| `grant_management_endpoint`          | グラント管理エンドポイントの URL                                                                  |
| `grant_management_action_required`   | `grant_management_action` リクエストパラメーターが常に要求されるかどうかを示す真偽値                             |

セキュリティ上の理由により、グラント管理はコンフィデンシャルクライアントのみしか利用できないので (*"grant management is restricted to confidential only clients due to security reasons"* ([Section 5.1](https://openid.net/specs/fapi-grant-management.html#name-requirements-for-authorizat)))、ディスカバリー文書 ([OIDC Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html)) が `"grant_management_action_required":true` を含んでいる場合、それは当認可サーバーの認可エンドポイントがパブリッククライアントからのリクエストを全て拒絶することを示唆しています。

# 実装

## 意思決定すべき事項

絶対的な答えを持たない多くの選択に実装者は直面することになるので、グラント管理仕様の実装はお互いかなり異なるものになるでしょう。下記はそのような選択の例です。

1. グラントのライフサイクルは、グラントを構成する要素と独立して管理すべきか、それとも要素に完全に依存するべきか。

2. 明示的なグラント管理アクション (`grant_management_action` を伴う認可リクエストおよびグラント管理エンドポイントへの失効リクエスト) を実行することなくグラントの内容は変化しうるのか、それとも明示的なアクションがなければ変化しないのか。

3. アクセストークンとリフレッシュトークンの権限は、紐付けられたグラントの内容が変化するのに伴って変化するのか、それとも発行後は変化しないのか。

4. リフレッシュトークンをどのようにグラントに紐付けるか。グラント失効時に関連するリフレッシュトークンも失効させなければならないのので、紐付けが必要。

5. グラント失効時、アクセストークンも失効させるかどうか。仕様ではアクセストークンも失効させるべきとしている。認可サーバーの実装によっては、発行後にアクセストークンを失効させることはできないことに注意。(参考: [OAuth アクセストークンの実装に関する考察](https://qiita.com/TakahikoKawasaki/items/970548727761f9e02bcd))

6. `grant_management_action=merge` で継承した権限をどのように管理するか。アクセストークン発行時のスナップショットとしてか (後から変化しない)、何か他の物へのリンクとしてか (後から変化するかもしれない)、またはその他の形態か。

7. `grant_management_action=merge` で継承した権限を持つアクセストークンの有効性をリソースサーバーはどのように確認するか。現在のところ、イントロスペクションレスポンスと JWT アクセストークンの形式についてグラント管理用に合意された標準は存在せず、議論も若干紛糾している。(参考: *"[FAPI ISSUE 455: Impact of grant\_management\_action=update on AT implementation and introspection](https://bitbucket.org/openid/fapi/issues/455)"*)

## 実装の実例

グラント管理は Authlete 2.3 以降でサポートされます。コミュニティへのフィードバックとして、Authlete の実装で採用した設計について記述していきます。

### 前提知識

下記のリストは、後続のセクションを読むのに必要となる Authlete のアクセストークンの実装に関する前提知識です。

1. アクセストークンの実装型は「ハンドル」であり、「内包型」(典型例は JWT) ではありません。「アクセストークン署名アルゴリズム」を設定することにより、Authlete に JWT アクセストークンを生成されることはできます (参照: *"[「JWT ベースのアクセストークン」の有効化](/v2/configuration-reference/token-issuance-formats/jwt-based-access-token)"*)。しかし、プライバシーおよびセキュリティ上の理由により、Authlete の JWT アクセストークンは紐付くデータ全てを含むことを意図的にせず、データの一部はアクセストークンに埋め込まれることなく Authlete のデータベース内のみに保持されます。詳細については *「[JWT アクセストークンからの個人情報漏洩](https://qiita.com/TakahikoKawasaki/items/119c7b6d6a62e7b0bfbd)」* および *「[OAuth アクセストークンの実装に関する考察](https://qiita.com/TakahikoKawasaki/items/970548727761f9e02bcd)」* を参照してください。

2. リフレッシュトークンフロー ([RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html) / [Section 6](https://www.rfc-editor.org/rfc/rfc6749.html#section-6)) 成功時、使用されたリフレッシュトークンに紐付くアクセストークンは、有効期限が切れていなくても失効します。

3. リフレッシュトークンフロー成功時に新しいリフレッシュトークンが発行されるか、もしくは発行されずに使用したリフレッシュトークンが使用後も有効のままかは、設定により選択可能です (参照: [リフレッシュトークンの継続利用設定](https://kb.authlete.com/ja/s/oauth-and-openid-connect/a/refreshing-refresh-tokens))。新しいリフレッシュトークンが発行される場合、使用されたリフレッシュトークンは、有効期限が切れていなくても失効します。

4. リフレッシュトークンとアクセストークンの関係は 1 対 1 です。つまり、1 つのリフレッシュトークンごとに有効なアクセストークンの数は最大でも 1 で、逆も成り立ちます。

5. リフレッシュトークンが削除された場合、対になっているアクセストークンも削除されます。同様に、アクセストークンが削除された場合、対になっているリフレッシュトークンも削除されます。後者の動作については驚く方もいるかもしれませんが、[RFC 7009 OAuth 2.0 Token Introspection](https://www.rfc-editor.org/rfc/rfc7009.html) の [Section 2.1](https://www.rfc-editor.org/rfc/rfc7009.html#section-2.1) は明示的に次のように述べています: *"If the token passed to the request is an access token, the server MAY revoke the respective refresh token as well."*

6. 上記のリフレッシュトークンの動作は、Authlete のかなり初期の段階でなされた設計に関する意思決定により説明できます: **アクセストークンとリフレッシュトークンの組はアクセストークンテーブル内の一つのレコードで管理される。**

7. アクセストークンレコードは、アクセストークンとリフレッシュトークンのどちらか、もしくは双方が有効期限前であれば、Live (期限切れしていない) とみなされます。

### グラント

**Authlete はグラントを Live アクセストークンレコードの集合と定義します。** 結果として、グラントのアクセストークンレコードが期限切れしたとき、グラントの内容が変化します。この変化は明示的なグラント管理アクション (`grant_management_action` を伴う認可リクエストおよびグラント管理エンドポイントへの失効リクエスト) がなくとも起こりえます。なぜなら、アクセストークンレコードの期限切れはグラント管理アクションとは独立して起こるからです。

認可サーバーの実装はグラント ID を管理するために新たにデータベーステーブルを作成することを選ぶかもしれません。しかし、Authlete は異なるアプローチを選択し、既存のアクセストークンテーブルに `grant_id` カラムを追加するだけで済ませました。下図は、グラントが Live アクセストークンレコードにより構成されることを示しています。

<img src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/protocols-and-flows/advanced-flows/live-access-token-records.ja.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=eac860a0fc1e09d992ec44543383c2fb" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/live-access-token-records.ja.png" />

### グラント ID 発行

初版ドラフトの [Section 5.4 Token Response](https://openid.net/specs/fapi-grant-management.html#name-token-response) が *"The AS will return a `grant_id` if it supports any of the grant management actions"* と言っているものの、Authlete では、トークンレスポンスがグラント ID を含むのは、先行する認可リクエスト (またはバックチャネル認証リクエストもしくはデバイス認可リクエスト) が `grant_management_action` リクエストパラメーターを含んでいる場合のみです。

仕様書の記述は結果的に、グラント管理をサポートする認可サーバーが無条件に常にグラント ID を発行することを要求することになっていますが、グラント管理を使用することを認めらていないパブリッククライアント ([Section 5.1](https://openid.net/specs/fapi-grant-management.html#name-requirements-for-authorizat)) にグラント ID を発行しても意味がありません。*"[FAPI ISSUE 455: Condition for a token response to include a grant\_id](https://bitbucket.org/openid/fapi/issues/445)"* で文言の変更を提案しています。

認可リクエストが `grant_management_action` リクエストパラメーターを含み、認可エンドポイントからアクセストークンが発行されるとしても、Authlete は認可エンドポイントからグラント ID を発行しません。なぜなら、仕様の著者の方々が意図的にそのケースを仕様から取り除いていることを確認できたからです。仕様内で明示的に言及するよう、*"[FAPI ISSUE 453: Grant ID from Authorization Endpoint](https://bitbucket.org/openid/fapi/issues/453)"* で提案しています。

一方、バックチャネル認証リクエストが `grant_management_action` リクエストパラメーターを含み、クライアントが CIBA プッシュモードを使用するように設定されている場合、Authlete は CIBA プッシュ通知にグラント ID を含めます。しかし、CIBA プッシュ通知用に `grant_id` パラメーターを登録することをまだ提案していません。いずれにしても、[FAPI-CIBA プロファイル](https://openid.net/specs/openid-financial-api-ciba.html)は CIBA プッシュモードの使用を禁止しているため、[FAPI ワーキンググループ](https://openid.net/wg/fapi/)はこの件に興味は示さないでしょう。

### 継承した権限

認可リクエストが `grant_management_action=merge` を含んでいるとき、新規発行されるアクセストークンは、`grant_id` リクエストパラメーターで特定されるグラントの権限を継承します。Authlete はこの継承を、既存のアクセストークンレコード群の権限を新規挿入されるアクセストークンレコードの `grant` カラムにコピーすることで実現します。

`grant` カラムの内容は、アクセストークン発行時点に存在した権限のスナップショットです。スナップショットは、権限の提供元のアクセストークンレコードが期限切れしたり削除されたりした後も変化しません。

<img src="https://mintcdn.com/authlete/oZd1VUaYFwHO2MBx/protocols-and-flows/advanced-flows/snapshot.ja.png?fit=max&auto=format&n=oZd1VUaYFwHO2MBx&q=85&s=0b40d944c1ff91e44185e15d763a3c24" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/snapshot.ja.png" />

<Note>
  **Authlete インターナルズ**

  実際のカラム名は異なります。継承した `authorization_details` 配列の内容が機微な情報を含みうるので、スナップショットは複数のカラムで構成され、暗号化されます。
</Note>

### ディスカバリー文書内のメタデータ

グラント管理関連のサーバーメタデータの値は下表が説明するとおりに決定されます。

| メタデータ                                | 値                                                                                                                  |
| :----------------------------------- | :----------------------------------------------------------------------------------------------------------------- |
| `grant_management_actions_supported` | グラント管理エンドポイントが設定されていれば \[`create`, `query`, `replace`, `revoke`, `merge`]。そうでなければ \[`create`, `replace`, `merge`]。 |
| `grant_management_endpoint`          | ウェブコンソールの「グラント管理エンドポイント」で設定可能                                                                                      |
| `grant_management_action_required`   | ウェブコンソールの「グラント管理アクションパラメーター」で設定可能                                                                                  |

<img src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/protocols-and-flows/advanced-flows/grant-management-configuration.ja.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=97364e3aeb48c4520701649396c27d37" alt="" width="1100" height="530" data-path="protocols-and-flows/advanced-flows/grant-management-configuration.ja.png" />

<Note>
  **Authlete インターナルズ**

  `authlete-server.properties` 内の `feature.gm.enabled` が `true` ではない場合、グラント管理関連のサーバーメタデータはディスカバリー文書内に含まれません。

  実際はそれ以上です。当該フラグが `true` でない場合、グラント管理関連のリクエストパラメーターは認識されず (単に無視され)、(開発者がグラント管理エンドポイントを実装するのに使う) `/api/gm` API は応答を拒否します。
</Note>

### 異なるユーザー

認可処理の過程でユーザーは (まだであれば) 認証されます。`grant_id` リクエストパラメーターで指定されたグラントのユーザーが認証されたユーザーと異なるということはありえます。そのような場合に認可サーバーがどの振る舞うべきかについて、仕様は述べていません。この件を議論するため *"[FAPI 452: When the user being authenticated is different from the user of the grant](https://bitbucket.org/openid/fapi/issues/452)"* を作成しましたが、結論としては「実装に任せる」となりそうです。

認証されたユーザーとグラントのユーザーが異なる場合、Authlete はエラーを報告することなく、グラント管理関連の処理を黙ってスキップします。

<Note>
  **Authlete インターナルズ**

  `/api/auth/authorization/issue` API に渡された `subject` リクエストパラメーターの値がグラントのユーザーの識別子と異なる場合、Authlete は認証されたユーザーとグラントのユーザーが異なるとみなします。同じロジックが `/api/backchannel/authentication/complete` API (CIBA 用) と `/api/device/complete` API (デバイスフロー用) にも適用されます。

  `grant_id` リクエストパラメーターをパースする Authlete API (`/api/auth/authorization`, `/api/backchannel/authentication`, `/api/device/authorization`) は、レスポンスにグラントに関する情報を含めます。`grantSubject` はそのようなレスポンスパラメーターの一つで、グラントのユーザーの識別子を表しています。(参考: `AuthorizationResponse.getGrantSubject()`)
</Note>

### 同意済みクレーム

設計により、認可サーバーとユーザーとのやりとりに Authlete は干渉しません。Authlete は認可サーバーではなく、開発者が自分の認可サーバーを開発するのに使う Web API のセットです。そのため、開発者が開発した認可サーバーがユーザーの同意をどのように取得するのか、Authlete は知りません。クレームセットに展開される特別なスコープ (`profile`, `email`, `address`, `phone`) が `scope` リクエストパラメーターに含まれているとき、認可サーバーは展開されたクレーム群について一つ一つ同意を取るかもしれませんし、その手続きを省略するかもしれません。Authlete は、提供されたクレームデータが、ユーザーの同意を得たものかどうかを知ることなく、ID トークンやユーザー情報レスポンスに組み込みます。

しかし、グラント管理エンドポイントの応答に含まれる `claims` プロパティーを意味あるものにするためには、Authlete は同意されたクレーム群の正確なセットを知るべきです。この目的のため、次の API に `consentedClaims` リクエストパラメーターが追加されました。(参照: `[AuthorizationIssueRequest.setConsentedClaims()](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/AuthorizationIssueRequest.html#setConsentedClaims-java.lang.String:A-)`)

* `/api/auth/authorization/issue`
* `/api/backchannel/authentication/complete`
* `/api/device/complete`

リクエストパラメーターが与えられ、値が空でなければ、Authlete はその値を同意済みクレーム群のセットとみなします。そうでない場合、Authlete は同意された特別なスコープ群 (例: `profile`) と (`claims` リクエストパラメーターにより) 提供されたクレームデータをもとに、同意済みクレーム群のセットを計算します。しかし、計算により得られるセットは実際に同意されたクレーム群のセットとは異なるかもしれません。特に、計算されたセットには、認可サーバーがユーザー情報エンドポイントから返すクレーム群は含まれていないかもしれません。そのため、同意済みクレーム群の正確なセットを制御する必要がある場合、`consentedClaims` リクエストパラメーターが使用されるべきです。

<Note>
  **Authlete インターナルズ**

  同意済みクレーム群のセットはアクセストークンテーブルの `consented_claims` カラムに保存されます。
</Note>

### グラント管理レスポンスの圧縮

Authlete はグラント管理レスポンスの内容を圧縮します。

#### scopes

`scopes` 配列内の要素はリソースセットごとにグループ化されます。要素はリソースセットでソートされます。同じリソースセット内のスコープはマージされて `scope` プロパティにセットされます。

例えば、グラントの Live アクセストークンレコード群が次の権限を持っている場合、

| scope       | resource          |
| :---------- | :---------------- |
| `"X23 L23"` | \[`"r2"`, `"r3"`] |
| `"X2  K2"`  | \[`"r2"`        ] |
| `"X3  J3"`  | \[`"r3"`        ] |
| `"X13 I13"` | \[`"r1"`, `"r3"`] |
| `"X12 H12"` | \[`"r1"`, `"r2"`] |
| `"X1  G1"`  | \[`"r1"`        ] |
| `"X3  F3"`  | \[`"r3"`        ] |
| `"X23 E23"` | \[`"r2"`, `"r3"`] |
| `"X13 D13"` | \[`"r1"`, `"r3"`] |
| `"X2  C2"`  | \[`"r2"`        ] |
| `"X1  B1"`  | \[`"r1"`        ] |
| `"X12 A12"` | \[`"r1"`, `"r2"`] |

当該集合は次のように圧縮されます。

| scope           | resource           |
| :-------------- | :----------------- |
| `"B1  G1  X1"`  | \[`"r1"`         ] |
| `"A12 H12 X12"` | \[`"r1"`, `"r2"` ] |
| `"D13 I13 X13"` | \[`"r1"`, `"r3"` ] |
| `"C2  K2  X2"`  | \[`"r2"`         ] |
| `"E23 L23 X23"` | \[`"r2"`, `"r3"` ] |
| `"F3  J3  X3"`  | \[`"r3"`         ] |

この結果、グラント管理レスポンス内の `scopes` 配列は次のようになります。

```json theme={null}
{
  "scopes": [
    { "scope": "B1 G1 X1",    "resource": [ "r1"       ] },
    { "scope": "A12 H12 X12", "resource": [ "r1", "r2" ] },
    { "scope": "D13 I13 X13", "resource": [ "r1", "r3" ] },
    { "scope": "C2 K2 X2",    "resource": [ "r2"       ] },
    { "scope": "E23 L23 X23", "resource": [ "r2", "r3" ] },
    { "scope": "F3 J3 X3",    "resource": [ "r3"       ] }
  ]
}
```

#### claims

グラントの Live アクセストークンレコード群の同意済みクレーム群は集められ、重複は取り除かれ、残った要素はソートされて `claims` 配列に格納されます。

例えば、グラントの Live アクセストークンレコード群が次の同意済みクレーム群を持っている場合、

| 同意済みクレーム群             |
| :-------------------- |
| \[ `c3`, `c5` ]       |
| \[ `c1`, `c3` ]       |
| \[ `c2`, `c4`, `c5` ] |

当該集合は次のように圧縮されます。

| consented claims                  |
| :-------------------------------- |
| \[ `c1`, `c2`, `c3`, `c4`, `c5` ] |

この結果、グラント管理レスポンス内の `claims` 配列は次のようになります。

```json theme={null}
{
  "claims": [
    "c1", "c2", "c3", "c4", "c5"
  ]
}
```

#### authorization\_details

`authorization_details` 配列内の重複は取り除かれます。Authlete は配列要素のハッシュ値を比較することで重複を検出します。JSON 内のスペースや改行はハッシュ計算の結果に影響しません。また、JSON オブジェクト内のキーの順序も結果に影響しません。そのため、次の二つの `authorization_details` 要素は同一とみなされます。

```json theme={null}
{
  "authorization_details": [
    {
      "type": "t1",
      "actions": [ "a1", "a2" ],
      "my_custom_data": {
        "key1": "value1",
        "key2": "value2"
      }
    },
    {
      "my_custom_data": { "key2": "value2", "key1": "value1" },
      "actions": [
        "a1",
        "a2"
      ],
      "type": "t1"
    }
  ]
}
```

### アクセストークンイントロスペクション

先に述べたとおり、スコープ群が意図しないリソース群と組み合わせられることがないよう、`merge` 操作の繰り返しにより権限が累積しても認可サーバーはスコープリソースクラスター群を分けて管理せざるをえません。

`merge` 操作により、アクセストークンはスコープリソースクラスター群を継承します。であれば、イントロスペクションレスポンス ([RFC 7662](https://www.rfc-editor.org/rfc/rfc7662.html)) と JWT アクセストークン ([RFC 9068](https://www.rfc-editor.org/rfc/rfc9068.html)) のフォーマットを拡張する必要があります。そうしないと、保護リソースエンドポイントはアクセストークンの有効性を適切に確認できないからです。

この件を議論するため、*"[FAPI ISSUE 455: Impact of grant\_management\_action=update on AT implementation and introspection](https://bitbucket.org/openid/fapi/issues/455)"* を作成し、また、議論の要点を詳細に説明するために *"[Complexity of Access Token Privileges Introduced by Grant Management](https://darutk.medium.com/complexity-of-access-token-privileges-introduced-by-grant-management-ec527b5c6d6a)"* という記事も書きました。

しかし、同意に達することは期待していたよりもはるかに大変でした😅。これを書いている時点では、本件の解決策についてコミュニティが短期間で合意に至るのは非現実的に思われます。

幸運なことに、Authlete は標準イントロスペクションエンドポイントとは異なる独自のイントロスペクション API (`/api/auth/introspection`) を開発者の方々に提供しています。当該 API は、標準のものよりも下記の点においてかなり便利です。

1. 証明書バインディング ([RFC 8705](https://www.rfc-editor.org/rfc/rfc8705.html))、[DPoP](https://datatracker.ietf.org/doc/draft-ietf-oauth-dpop/)、リソース ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html))、スコープ ([RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html))、リソースオーナーのサブジェクト、などの有効性を確認する複雑な処理を、イントロスペクション API に実施させることができる。

2. エラーの場合、保護リソースエンドポイントが [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750.html) に簡単に準拠できるように、イントロスペクション API は当仕様に準拠するエラーメッセージを用意する。

3. 開発者は Authlete の Extra Properties 機能を用いて任意の Key-Value ペア群をアクセストークンに紐付けることができる (参照: [トークンに任意のプロパティをひもづける方法](https://kb.authlete.com/ja/s/oauth-and-openid-connect/a/extra-properties))。イントロスペクション API の応答はその Key-Value ペア群を含んでいる。

Authlete はグラント管理のためにイントロスペクション API を拡張しました。

まず、新しいレスポンスパラメーター `grantId` と `grant` を追加しました。

| パラメーター    | 説明                                                                                                                                                                                                                   |
| :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `grantId` | アクセストークンに紐付くグラント ID。当アクセストークン用の認可リクエストが `grant_management_action` を含んでいた場合、このパラメーターの値は非 null となる。                                                                                                                    |
| `grant`   | 継承した権限。当アクセストークン用の認可リクエストが `grant_management_action=merge` を含んでいた場合、このパラメーターの値は非 null となる。値のフォーマットは Java クラス `[Grant](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/Grant.html)` により表現される。 |

次に、イントロスペクション API は新しいリクエストパラメーター `resources` を認識します。そして、`scopes` と `resources` 用の検証処理は一から書き直され、下表で説明するとおりに動作します。

| scopes | resources | 動作                                                                                                                                                                                                                               |
| :----: | :-------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   無し   |     無し    | スコープとリソースについて何も検証処理を実行しない。                                                                                                                                                                                                       |
|   有り   |     無し    | 指定された全てのスコープがアクセストークンによってカバーされるかどうか確認する。含有確認はスコープリソースクラスターごとに実行される。例えば、アクセストークンが `"scope":"s1"` と `"scope":"s2"` という二つのクラスターを保持しており、`scopes` リクエストパラメーターの値が \[`s1`, `s2`] であった場合、検証は失敗する。後方互換性のため、スコープリソースクラスターのリソースは考慮されない。     |
|   無し   |     有り    | 指定された全てのリソースがアクセストークンによってカバーされるかどうか確認する。含有確認はスコープリソースクラスターごとに実行される。例えば、アクセストークンが `"resource":["r1"]` と `"resource":["r2"]` という二つのクラスターを保持しており、`resources` リクエストパラメーターの値が \[`r1`, `r2`] であった場合、検証は失敗する。スコープリソースクラスターのスコープは考慮されない。 |
|   有り   |     有り    | 指定された全てのスコープとリソースがアクセストークンによってカバーされるかどうか確認する。含有確認はスコープリソースクラスターごとに実行される。                                                                                                                                                         |

### グラント管理エンドポイント実装

新しい API `/api/gm` が追加されました。開発者はこの API を用いてグラント管理エンドポイントを実装することができます。

<img src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/protocols-and-flows/advanced-flows/grant-management-endpoint-implementation.ja.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=2947c71d86c7d1333f799e0c5b7e26d6" alt="" width="1920" height="1080" data-path="protocols-and-flows/advanced-flows/grant-management-endpoint-implementation.ja.png" />

下表は `/api/gm` API のリクエストパラメーターのリストです。詳細は `[GMRequest](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/GMRequest.html)` クラスの JavaDoc を参照してください。

| パラメーター              |  要否 | 説明                                                                                                                                                       |
| :------------------ | :-: | :------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gmAction`          |  必須 | `QUERY` または `REVOKE`                                                                                                                                     |
| `grantId`           |  必須 | グラント ID                                                                                                                                                  |
| `accessToken`       |  必須 | アクセストークン                                                                                                                                                 |
| `clientCertificate` |  任意 | \[[RFC 8705](https://www.rfc-editor.org/rfc/rfc8705.html)] 相互 TLS 接続内のクライアント証明書                                                                          |
| `dpop`              |  任意 | \[[DPoP](https://datatracker.ietf.org/doc/draft-ietf-oauth-dpop/)] DPoP proof JWT                                                                        |
| `htm`               |  任意 | \[[DPoP](https://datatracker.ietf.org/doc/draft-ietf-oauth-dpop/)] HTTP メソッド (Authlete は `gmAction` からデフォルト値を計算可能)                                       |
| `htu`               |  任意 | \[[DPoP](https://datatracker.ietf.org/doc/draft-ietf-oauth-dpop/)] エンドポイントの URL (Authlete は `Service.grantManagementEndpoint` と `grantId` からデフォルト値を計算可能) |

# さいごに

Grant Management for OAuth 2.0 は Authlete 2.3 以降でサポートされます。現時点では、当バージョンは共有サーバー (`api.authlete.com`) にデプロイされていません。Authlete 2.3 へのアーリーアクセスをご希望の場合は[お問い合わせ](https://www.authlete.com/ja/contact/)ください。

*2021 年 11 月 9 日*

*文責: 川崎 貴彦*
