実装者による OIDC4IDA 解説

# はじめに

**OpenID Connect for Identity Assurance 1.0** (**OIDC4IDA** または **IDA**) は、[OpenID Foundation](https://openid.net/) の [eKYC-IDA ワーキンググループ](https://openid.net/wg/ekyc-ida/)が開発した技術仕様です。当仕様は **OAuth 2.0** と **OpenID Connect** (**OIDC**) を基盤とし、自然人の**検証済みクレーム**を格納する JSON 構造を定義します。

![ida-stack_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/312b4db9-8ad8-2854-1ba8-8c3b1058831f.png)

ここでの『**クレーム**』は技術用語で、名前、誕生日、住所等のユーザー属性を意味しています。

ここでの『**検証済み**』とは、パスポートや運転免許証等の証拠によってクレームの真正性が検証済みであることを意味しています。

インターネット上にはユーザー登録可能なウェブサービスがたくさんあります。それらの多くは属性情報の登録をユーザーに求めますが、登録されるデータはユーザーが自己申告したものに過ぎず、法的な文脈では利用できません。ここで OIDC4IDA が登場します。

※ 本記事の英語版はこちら → _**"[OpenID Connect for Identity Assurance, explained by an implementer](https://darutk.medium.com/oidc4ida-93aedffa3058)"**_

# 歴史

- **[実装者向け草稿第一版](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID1.html)**： 2019 年 11 月に[公表](https://openid.net/2019/11/11/implementers-draft-of-openid-connect-for-identity-assurance-specification-approved/)された仕様の初版です。この版は Implementer's Draft 1 (ID1) と呼ばれます。

- **[実装者向け草稿第二版](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html)**： 2020 年 5 月に[公表](https://openid.net/2020/05/19/second-implementers-draft-of-openid-connect-for-identity-assurance-specification-approved/)された二番目の版です。この版は Implementer's Draft 2 (ID2) と呼ばれます。

- **[実装者向け草稿第三版](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID3.html)**： 2021 年 11 月に[公表](https://openid.net/2021/11/11/third-implementers-draft-of-openid-connect-for-identity-assurance-specification-approved/)された三番目の版です。この版は Implementer's Draft 3 (ID3) と呼ばれます。本記事執筆時点 (2022 年 5 月) では、公式に公開された草稿の中では最新版です。

- **次の草稿**： これまでの実装者向け草稿は破壊的変更を繰り返してきました。そして再び、次の版にも破壊的変更が含まれることが確定しています。というのは、既に破壊的変更 (例：[PR 87](https://bitbucket.org/openid/ekyc-ida/pull-requests/87)) が [eKYC-IDA WG の Git レポジトリ](https://bitbucket.org/openid/ekyc-ida/)  の master ブランチにマージ済みだからです。破壊的変更と追加された新しいプロパティー群の量を考慮すると、将来の最終版の前に実装者向け草稿第四版が公開される可能性はかなり高いでしょう。

![ida-history_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/c64586f5-acdf-ed5e-a2db-4362be60ff8e.png)

本記事の執筆者 (私) は eKYC-IDA WG の定例会議に出席しており、仕様の最新情報を読者の皆様と共有することが可能なので、本記事は eKYC-IDA Git レポジトリ内の最新仕様に基づいて書くことにします。

# verified_claims

仕様は検証済みクレームを格納する JSON 構造を定義します。その JSON 構造は、**ID トークン** ([OIDC Core](https://openid.net/specs/openid-connect-core-1_0.html), [Section 2](https://openid.net/specs/openid-connect-core-1_0.html#IDToken)) のペイロード部や**ユーザー情報エンドポイント** ([OIDC Core](https://openid.net/specs/openid-connect-core-1_0.html), [Section 5.3](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo)) からのレスポンスなどの他の JSON 構造に埋め込まれることを想定しています。

当 JSON 構造が他の JSON 構造内に埋め込まれる際は、`"verified_claims"` というプロパティー・キーが与えられます。次の図は ID トークンのペイロード部にどのように埋め込まれるかを示しています。

![verified_claims_in_id_token.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/ec01f8a4-75c1-8939-38c0-1bba6746f0db.png)

`"verified_claims"` の構造は JSON スキーマファイル [verified_claims.json](https://bitbucket.org/openid/ekyc-ida/src/master/schema/verified_claims.json) に記述されています。このファイルは eKYC-IDA Git レポジトリの [schema](https://bitbucket.org/openid/ekyc-ida/src/master/schema/) ディレクトリで管理されています。

# verification と claims

`"verified_claims"` JSON オブジェクトは、`"verification"` と `"claims"` をトップレベルのプロパティーとして含んでいます。

`"verification"` JSON オブジェクトは、検証プロセスに関する情報、例えば「どの法規に基づくものか？」「どの証拠を使って？」「いつ？」「誰によって？」という情報を含んでいます。

`"claims"` JSON オブジェクトは、検証済みクレーム群の名前と値の組を含んでいます。

![verification-and-claims.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/3690d7ab-d9f9-5afc-8607-e9846711478b.png)

# verified_claims リクエスト

ID トークンやユーザー情報レスポンスに `"verified_claims"` を埋め込むよう認可サーバーに要求するため、クライアントアプリケーションは [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) の [Section 5.5](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter) で定義されている **`claims`** リクエストパラメーターを使います。

`claims` リクエストパラメーターのフォーマットは JSON です。その JSON には、`"id_token"` と `"userinfo"` のどちらかまたは両方がトップレベルのプロパティーとして含まれます。クライアントアプリケーションは、ID トークンに埋め込んでほしいクレーム群の名前を JSON オブジェクト `"id_token"` 内に列挙します。同様に、ユーザー情報レスポンスに埋め込んでほしいクレーム群の名前を JSON オブジェクト `"userinfo"` 内に列挙します。この規則は OIDC Core 仕様に書かれています。

![claims-request-parameter_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/df063578-2ce1-e176-a002-d12fe7dceb13.png)

検証済みクレームを取得したければ、下図が示すように、クライアントアプリケーションは JSON オブジェクト `"id_token"` や JSON オブジェクト `"userinfo"` に `"verified_claims"` を含めます。

![verified_claims-in-claims_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/98458499-14f3-3bf3-ac1c-ff601ba993c6.png)

`"verified_claims"` リクエストの構造は JSON スキーマファイル [verified_claims_request.json](https://bitbucket.org/openid/ekyc-ida/src/master/schema/verified_claims_request.json) に記述されています。このファイルは eKYC-IDA Git レポジトリの [schema](https://bitbucket.org/openid/ekyc-ida/src/master/schema/) ディレクトリで管理されています。

# データ最小化

サーバーは明示的に要求されていないデータを返却してはならないと仕様で定められています。このポリシーは**データ最小化** (**data minimization**) と呼ばれます。そのため、次のような単純な `verified_claims` リクエストに対しては、

```json
{
  "userinfo": {
    "verified_claims": {
      "verification": {
        "trust_framework": null
      },
      "claims": {
        "given_name": null
      }
    }
  }
}
```

次に示すように、サーバーは `"trust_framework"` と `"given_name"` しか返しません。

```json
{
  "verified_claims": {
    "verification": {
      "trust_framework": "nist_800_63A"
    },
    "claims": {
      "given_name": "Inga"
    }
  }
}
```

クライアントアプリケーションがもっと情報をほしければ、複雑な `verified_claims` リクエストを構築しなければなりません。次のものは (仕様書から抜粋した) 複雑な `verified_claims` リクエストを含む `claims` パラメーターの値の例です。

```json
{
  "userinfo": {
    "verified_claims": {
      "verification": {
        "trust_framework": null,
        "time": null,
        "evidence": [
          {
            "type": {
              "value": "document"
            },
            "method": null,
            "document_details": {
              "type": null
            }
          }
        ]
      },
      "claims": {
        "given_name": null,
        "family_name": null,
        "birthdate": null
      }
    }
  }
}
```

# フィルタリング

## `value`, `values`, `max_age` による制約

`verified_claims` リクエスト内のクレームが、`"value"`, `"values"`, `"max_age"` を含む JSON オブジェクトを伴い、かつ、それが `verified_claims/verification` 以下にあるなら (つまり `verified_claims/claims` 以下でなければ)、その JSON オブジェクトはフィルタリングの条件として使用されます。

|プロパティー名|説明|
|:--|:--|
|`"value"`|クレームの実際の値は `"value"` で指定された値と一致しなければならない。|
|`"values"`|クレームの実際の値は `"values"` 配列内のいずれかの要素と一致しなければならない。|
|`"max_age"`|クレームの実際の値が示す日時からの経過時間は `"max_age"` で指定された秒数を超えてはならない。|

### `value` の例

次の例は、トラストフレームワーク `uk_tfida` のルールで検証された検証済みクレームを要求します。サーバーが条件を満たす検証済みクレームを保持していない場合、`"verified_claims"` は返却されません。

```json
{
  "userinfo": {
    "verified_claims": {
      "verification": {
        "trust_framework": {
          "value": "uk_tfida"
        }
      },
      "claims": {
        "given_name": null
      }
    }
  }
}
```

### `values` の例

次の例は、トラストフレームワーク `nist_800_63A` または `uk_tfida` のルールで検証された検証済みクレームを要求します。

```json
{
  "userinfo": {
    "verified_claims": {
      "verification": {
        "trust_framework": {
          "values": [ "nist_800_63A", "uk_tfida" ]
        }
      },
      "claims": {
        "given_name": null
      }
    }
  }
}
```

### `max_age` の例

次の例は、アイデンティティ検証プロセスが実施された日時からの経過時間が 30 日 (= 2,592,000 秒) を超えていないことを要求します。サーバーが 30 日以内に検証された検証済みクレームを保持していない場合、`"verified_claims"` は返却されません。

```json
{
  "userinfo": {
    "verified_claims": {
      "verification": {
        "trust_framework": null,
        "time": {
          "max_age": 2592000
        }
      },
      "claims": {
        "given_name": null
      }
    }
  }
} 
```

## 配列による論理和

仕様の「[Requesting Verification Data](https://openid.net/specs/openid-connect-4-identity-assurance-1_0.html#name-requesting-verification-dat)」セクションには `evidence` 配列に対する興味深い要求事項があります。

> A single entry in the `evidence` array represents a filter over elements of a certain evidence type. The RP therefore MUST specify this type by including the `type` field including a suitable `value` sub-element value. The `values` sub-element MUST NOT be used for the `evidence/type` field.
>
>If multiple entries are present in `evidence`, these filters are linked by a logical OR.

結果として、次のリクエストにより、`"type"` が `"document"` もしくは "`electronic_signature"` である証拠が全て `"verified_claims"` に含まれることになります。

```json
{
  "userinfo": {
    "verified_claims": {
      "verification": {
        "trust_framework": null,
        "evidence": [
          {
            "type": {
              "value": "document"
            },
            "method": null,
            "document_details": {
              "type": null,
              "document_number": null
            }
          },
          {
            "type": {
              "value": "electronic_signature"
            },
            "signature_type": null,
            "issuer": null,
            "serial_number": null
          }
        ]
      },
      "claims": {
        "given_name": null
      }
    }
  }
}
```

この特別規則は `check_details` などの他の配列型プロパティー群にも適用されます。しかし、`assurance_details` については特別規則があります。下記は当規則を仕様書から抜粋したものです。

>`assurance_details` is an array representing how the `evidence` and `check_details` meets the requirements of the `trust_framework`. RP SHOULD only request this where they need to know this information. Where `assurance_details` have been requested by an RP the OP MUST return the `assurance_details` element along with all sub-elements that it has. If an RP wants to filter what types of `evidence` and `check_methods` they MUST use those methods to do so, e.g. requesting an `assurance_type` should have no filtering effect.

つまり、`assurance_details` を要求すると、当プロパティーの全ての下位要素が返却されます。データ最小化や配列による論理和は適用されません。

## データ最小化とフィルタリングの例

次の JSON の内容と同等のデータセットをサーバーが保持しているものとします。

```json
{
  "verification": {
    "trust_framework": "uk_tfida",
    "evidence": [
      {
        "type": "electronic_record",
        "check_details": [
          {
            "check_method": "kbv",
            "organization": "TheCreditBureau",
            "txn": "kbv1-hf934hn09234ng03jj3"
          }
        ],
        "time": "2021-04-09T14:12Z",
        "record": {
          "type": "mortgage_account",
          "source": {
            "name": "TheCreditBureau"
          }
        }
      },
      {
        "type": "electronic_record",
        "check_details": [
          {
            "check_method": "kbv",
            "organization": "OpenBankingTPP",
            "txn": "kbv2-nm0f23u9459fj38u5j6"
          }
        ],
        "time": "2021-04-09T14:12Z",
        "record": {
          "type": "bank_account",
          "source": {
            "name": "TheBank"
          }
        }
      }
    ]
  },
  "claims": {
    "given_name": "Sarah",
    "family_name": "Meredyth",
    "birthdate": "1976-03-11",
    "place_of_birth": {
      "country": "UK"
    },
    "address": {
      "locality": "Edinburgh",
      "postal_code": "EH1 9GP",
      "country": "UK",
      "street_address": "122 Burns Crescent"
    }
  }
}
```

このとき、当該データセットに対して次の `verified_claims` リクエストが行われた場合、

```json
{
  "verification": {
    "trust_framework": null,
    "evidence": [
      {
        "type": {
          "value": "electronic_record"
        },
        "check_details": [
          {
            "check_method": null,
            "organization": {
              "value": "OpenBankingTPP"
            },
            "txn": null
          }
        ]
      }
    ]
  },
  "claims": {
    "given_name": null,
    "family_name": {
      "value": "Unknown"
    },
    "address": {
      "locality": null
    }
  }
}
```

サーバーは次の `"verified_claims"` を生成すべきです。

```json
{
  "verification": {
    "trust_framework": "uk_tfida",
    "evidence": [
      {
        "type": "electronic_record",
        "check_details": [
          {
            "check_method": "kbv",
            "organization": "OpenBankingTPP",
            "txn": "kbv2-nm0f23u9459fj38u5j6"
          }
        ]
      }
    ]
  },
  "claims": {
    "given_name": "Sarah",
    "address": {
      "locality": "Edinburgh"
    }
  }
}
```

要点は次のとおりです。

1. サーバー側のデータセットでは `"evidence"` 配列に要素が 2 つ含まれているものの、生成された `"verified_claims"` には要素が 1 つしかありません。これは、`check_details/*/organization == "OpenBankingTPP"` という制約条件を満たさない要素が取り除かれたためです。

2. 生成された `"verified_claims"` には `"family_name"` クレームが含まれていません。これは、サーバー側のデータセットにおいて、`"family_name"` の実際の値が `"Unknown"` ではないからです。`"value"`, `"values"`, `"max_age"` 制約が `verified_claims/verification` 以下で使われた場合、不一致のプロパティーを含むデータセットの全体または一部が省略されます。一方で、`verified_claims/claims` 以下では、不一致のプロパティーは他の部分に影響を与えることなく、それのみが省略されます。

3. データ最小化ポリシーのため、生成された `"verified_claims"` では多くのプロパティーが省略されています。例えば、`"address"` には `"locality"` しか含まれていません。`"postal_code"`, `"country"`, `"street_address"` といった他のプロパティーは省略されています。

## アタッチメント

実装者向け草稿第三版で追加されたものの中でも、**アタッチメント**は比較的大きな機能です。この機能により、アイデンティティ検証プロセスに関連のあるメディアを添付することが可能になります。例えば、署名フォームのスキャンや証拠の写真などです。

アタッチメントは、下記が示すように `"attachments"` 配列に直接埋め込むことができます。次の例は eKYC-IDA Git レポジトリにある [embedded_attachments.json](https://bitbucket.org/openid/ekyc-ida/src/master/examples/response/embedded_attachments.json) のコピーです。

```json
{
  "verified_claims": {
    "verification": {
      "trust_framework":"eidas",
      "assurance_level": "substantial",
      "evidence": [
        {
          "type": "document",
          "method": "pipp",
          "time": "2012-04-22T11:30Z",
          "document_details": {
            "type": "idcard",
            "issuer": {
              "name": "Stadt Augsburg",
              "country": "DE"
            },
            "document_number": "53554554",
            "date_of_issuance": "2010-03-23",
            "date_of_expiry": "2020-03-22"
          },
          "attachments": [
            {
              "desc": "Front of id document",
              "content_type": "image/png",
              "content": "Wkd0bWFtVnlhWFI2Wlc0Mk16VER2RFUyY0RRMWFUbDBNelJ1TlRjd31dzdaM1pTQXJaWGRsTXpNZ2RETmxDZwo="
            },
            {
              "desc": "Back of id document",
              "content_type": "image/png",
              "content": "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAADSFsjdkhjwhAABJRU5ErkJggg=="
            }
          ]
        }
      ]
    },
    "claims": {
      "given_name": "Max",
      "family_name": "Mustermann",
      "birthdate": "1956-01-28"
    }
  }
}
```

また、アタッチメントの内容がリモートサーバー上に置かれているケースでは、アタッチメントの URL のみを (任意でアクセストークンを添えて) `"attachments"` 配列に含めることができます。次の例は eKYC-IDA Git レポジトリにある [external_attachments.json](https://bitbucket.org/openid/ekyc-ida/src/master/examples/response/external_attachments.json) のコピーです。

```json
{
  "verified_claims": {
    "verification": {
      "trust_framework":"eidas",
      "assurance_level": "substantial",
      "evidence": [
        {
          "type": "document",
          "method": "pipp",
          "time": "2012-04-22T11:30Z",
          "document_details": {
            "type": "idcard",
            "issuer": {
              "name": "Stadt Augsburg",
              "country": "DE"
            },
            "document_number": "53554554",
            "date_of_issuance": "2010-03-23",
            "date_of_expiry": "2020-03-22"
          },
          "attachments": [
            {
              "desc": "Front of id document",
              "digest": {
                "alg": "sha-256",
                "value": "qC1zE5AfxylOFLrCnOIURXJUvnZwSFe5uUj8t6hdQVM="
              },
              "url": "https://example.com/attachments/pGL9yz4hZQ",
              "access_token": "ksj3n283dke",
              "expires_in": 30
            },
            {
              "desc": "Back of id document",
              "digest": {
                "alg": "sha-256",
                "value": "2QcDeLJ/qeXJn4nP+v3nijMgxOBCT9WJaV0LjRS4aT8="
              },
              "url": "https://example.com/attachments/4Ag8IpOf95"
            },
            {
              "desc": "Signed document",
              "digest": {
                "alg": "sha-256",
                "value": "i3O7U79LiyKmmesIgULKT2Q8LAxNO0CpwJVcbepaYf8="
              },
              "url": "https://example.com/attachments/4Ag8IpOf95",
              "access_token": null,
              "expires_in": 30
            }
          ]
        }
      ]
    },
    "claims": {
      "given_name": "Max",
      "family_name": "Mustermann",
      "birthdate": "1956-01-28"
    }
  }
}
```

# 変換クレーム

**変換クレーム** (**Transformed Claim**; **TC**) は OIDC4IDA 仕様の一部ではありませんが、eKYC-IDA WG で議論されている仕様群の一つである [OpenID Connect Advanced Syntax for Claims (ASC) 1.0](https://bitbucket.org/openid/ekyc-ida/src/master/openid-advanced-syntax-for-claims.md) の一部です。

## 紹介

変換クレーム機能は、**変換関数** (**transformation function**) を適用することでクレーム値の変換を可能にします。

例えば、`birthdate` クレームに `years_ago` 変換関数を適用してユーザーの年齢を計算し、ID トークンやユーザー情報レスポンスに埋め込むことができます。さらに、`years_ago` 変換関数の結果に対して、引数 `18` を伴う `gte` 変換関数 (`gte` は "Greater Than or Equal to" の意) を適用することで、ユーザーが 18 歳以上かどうかを示す真偽値を得ることができます。この例は、「**年齢確認** (**age verification**) が必要なものの誕生日等の個人情報は極力開示すべきではない」というシナリオで使うことができます。

![tc-concept_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/588721d2-f989-4f00-4b09-2d3b657ff243.png)

TC 仕様に注目すべき理由は、インターネット上に世界規模の高信頼デジタルアイデンティティネットワークの構築を目指す **GAIN** (**Global Assured Identity Network**) プロジェクトの[ホワイトペーパー](https://gainforum.org/GAINWhitePaper.pdf)に載せられているシナリオで年齢確認が鍵となる要素の一つとなっているからです。

## 変換クレームの定義

変換クレームは次の要素で成り立っています。

1. 変換クレームの名前
2. 入力として参照するクレーム
3. 入力に対して適用する変換関数群 (引数を必要とするものもある)

『変換クレームの例』の図で、`age_18_or_over` が変換クレームの名前、`birthdate` が参照するクレーム、`years_ago` と (引数 `18` を伴う) `gte` が適用する変換関数群です。

変換クレームは JSON オブジェクト内のプロパティーとして定義されます。プロパティーのキーが変換クレームの名前で、プロパティーのバリューは `claim` プロパティーと `fn` プロパティーを含む JSON オブジェクトです。`claim` プロパティーは参照するクレームを示し、`fn` プロパティーは変換関数のリストです。

例えば、`age_18_or_over` 変換クレームは次のように定義することができます。

```json
{
  "age_18_or_over": {
    "claim": "birthdate",
    "fn": [
      "years_ago",
      [ "gte", 18 ]
    ]
  }
}
```

`fn` プロパティーの値は配列です。配列の各要素は変換関数であり、引数が伴っている場合もあります。変換関数が引数を取らない場合は変換関数の名前のみが `fn` 配列内に書かれます (例: `"years_ago"`)。一方、変換関数が引数を取る場合、変換関数の名前と引数群をまとめた配列が `fn` 配列内に書かれます (例: `["gte", 18]`)。

## 変換クレームの定義を置く場所

変換クレームの定義を置く場所は 2 つあります。一つは認可リクエストの `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)) です。もう一方は認可サーバーのディスカバリードキュメント ([OIDC Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html), [Section 4](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig)) です。

### `transformed_claims`

仕様では、`claims` リクエストパラメーター内の新しいプロパティーとして `transformed_claims` が定義されます。クライアントアプリケーションは変換クレーム群の定義をそこに書きます。

次の JSON は `claims` リクエストパラメーターの内容の例です。`age_18_or_over` 変換クレームの定義を含む `"transformed_claims"` が含まれています。

```json
{
  "transformed_claims": {
    "age_18_or_over": {
      "claim": "birthdate",
      "fn": [
        "years_ago",
        [ "gte", 18 ]
      ]
    }
  },
  "id_token": {
    "verified_claims": {
      "verification": {
        "trust_framework": null
      },
      "claims": {
        "given_name": null,
        ":age_18_or_over": null
      }
    }
  }
}
```

この例でもう一つ言及しておくべき点は、定義された変換クレームの参照のされ方です。`"transformed_claims"` で定義された変換クレームが `"claims"` 内で参照されるとき、`:age_18_or_over` のように、コロン (`:`) が前置されます。

### `transformed_claims_predefined`

仕様は、新しいサーバーメタデータとして `transformed_claims_predefined` を定義しています。認可サーバーは変換クレームの定義をそこに置くことができます。

次の JSON は `transformed_claims_predefined` サーバーメタデータの例です。

```json
{
  "transformed_claims_predefined": {
    "age_100_or_over": {
      "claim": "birthdate",
      "fn": [
        "years_ago",
        [ "gte", 100 ]
      ]
    }
  }
}
```

クライアントアプリケーションは、認可サーバーが事前定義した変換クレームを再定義することなく利用可能です。次の JSON は `claims` リクエストパラメーターの値の例で、二つのコロン (`::`) を前置することにより事前定義された変換クレームを参照しています。

```json
{
  "id_token": {
    "verified_claims": {
      "verification": {
        "trust_framework": null
      },
      "claims": {
        "given_name": null,
        "::age_100_or_over": null
      }
    }
  }
}
```

## 変換関数の連結

変換クレーム定義内の `fn` プロパティーは変換関数のリストです。リスト内の一番目の変換関数は既知のクレームを入力として受け取ります。二番目以降の変換関数は、それぞれの直前の変換関数の実行結果を入力として受け取ります。最後の変換関数の実行結果は、変換クレームの値として使用されます。

例えば、先に挙げた例では、`years_ago` 変換関数が `birthdate` クレームの値を受け取り、実行結果として、例えば `30` を生成します。その後、`gte` 変換関数が `30` を入力として受け取り、実行結果として `true` を生成します。`gte` が生成したこの `true` が、`age_18_or_over` 変換クレームの値となります。

![tc-chain_ja.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/ae206190-7110-c1fb-2373-36e85792c68e.png)

## 定義済みの変換関数

次の変換関数群は仕様で事前定義されています。詳細については [ASC 仕様](https://bitbucket.org/openid/ekyc-ida/src/master/openid-advanced-syntax-for-claims.md)を参照してください。

| 変換関数 | 説明 |
|:--|:--|
| `years_ago` | 入力値からの経過年数を計算する。 |
| `eq`    | 入力値と引数が等しければ真。 |
| `gt`    | 入力値が引数よりも大きければ真。 |
| `lt`    | 入力値が引数よりも小さければ真。 |
| `gte`   | 入力値が引数以上であれば真。 |
| `lte`   | 入力値が引数以下であれば真。 |
| `hash`  | 入力値のハッシュ値を計算する。 |
| `any`   | 入力配列のいずれかの要素が真ならば真。 |
| `all`   | 入力配列の全ての要素が真ならば真。 |
| `none`  | 入力配列の全ての要素が偽ならば真。 |
| `get`   | 入力 JSON オブジェクトから指定されたプロパティーの値を取得する。 |
| `match` | 入力値が引数で指定された正規表現にマッチすれば真。 |

## 変換クレームのデモ

2021 年 12 月 1 日、OAuth Security Workshop のセッションで本記事の筆者 (私) が変換クレームのデモを行いました。その録画が YouTube にあがっています。デモは [28 分 32 秒](https://www.youtube.com/watch?v=DQqsuJwYXmc&t=1712s)から始まります。

<iframe width="560" height="315" src="https://www.youtube.com/embed/DQqsuJwYXmc?start=1712" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# 実装

## データ最小化とフィルタリングの実装

OIDC4IDA 仕様を妥協せずに実装し始めるとすぐに、データ最小化ポリシーとフィルタリング規則のロジックを実装するのが難しいことに気付くでしょう。

OIDC Core では、(`address` クレーム を除き) 全てのクレームは ID トークンのペイロード部やユーザー情報レスポンスのトップレベルプロパティーとしてフラットに列挙されます。しかし、`"verified_claims"` リクエストの構造は複雑過ぎます。

落胆しているかもしれない開発者の方々のため、当該ロジックの実装をオープンソースとして公開しました。

- [DatasetExtractor.java](https://github.com/authlete/authlete-java-common/blob/master/src/main/java/com/authlete/common/ida/DatasetExtractor.java)

`DatasetExtractor` クラスは [authlete-java-common](https://github.com/authlete/authlete-java-common/) ライブラリの一部です。当ライブラリは **[Authlete](https://www.authlete.com/ja/)** 用ですが、当クラス内に実装されているロジックは [Authlete API](https://docs.authlete.com/) からは独立しています。そのため、ロジックをあなたの環境に移植することは可能です。

## 変換クレームの実装

私の知る限り、本記事執筆時点 (2022 年 5 月) において、変換クレームの実装は世界に Authlete 2.3 しかありません。変換クレームの機能は Authlete サーバーに実装されており、オープンソースではありません。変換クレームを試したい方は [Authlete 社までお問い合わせ](https://www.authlete.com/ja/contact/)ください。

## 認可サーバーの実装

[java-oauth-server](https://github.com/authlete/java-oauth-server/) はオープンソースの認可サーバーサンプル実装です。java-oauth-server は Authlete をバックエンドシステムとして使用します。Authlete はフロントエンドの認可サーバーに代わり、OAuth/OIDC リクエストの解釈、アクセストークンの生成管理、OAuth/OIDC レスポンスの準備をおこないます。

最新の IDA 仕様と変換クレームをサポートする Authlete 2.3 を使うように java-oauth-server を設定すれば、この記事で説明されている仕様を試すことができます。

次のものは java-oauth-server に対する認可リクエストの例です。

```
http://localhost:8080/api/authorization?client_id=7681191256&redirect_uri=http://localhost:4000/api/mock/redirection/5383012317&response_type=id_token&scope=openid+email&state=q4sa5o7g6y&nonce=n-0S6_WzA2Mj&claims={"id_token":{"verified_claims":{"verification":{"trust_framework":null,"assurance_process":{"assurance_details":[{"assurance_type":{"value":"verification"},"assurance_classification":null,"evidence_ref":{"txn":null}}]},"verification_process":null,"evidence":[{"type":{"value":"document"},"check_details":[{"check_method":{"value":"pvp"},"organization":null,"txn":null}],"time":null}]},"claims":{"given_name":null,"family_name":null,"address":{"street_address":null,"locality":null,"country":null},"::age_100_or_over":null,":age_18_or_over":null}}},"transformed_claims":{"age_18_or_over":{"claim":"birthdate","fn":["years_ago",["gte",18]]}}}
```

次の jSON は、上記の認可リクエストの `claims` パラメーターの値を見やすく整形したものです。

```json
{
  "id_token": {
    "verified_claims": {
      "verification":{
        "trust_framework": null,
        "assurance_process": {
          "assurance_details": [
            {
              "assurance_type": {
                "value": "verification"
              },
              "assurance_classification": null,
              "evidence_ref": {
                "txn": null
              }
            }
          ]
        },
        "verification_process": null,
        "evidence": [
          {
            "type": {
              "value": "document"
            },
            "check_details": [
              {
                "check_method": {
                  "value": "pvp"
                },
                "organization": null,
                "txn": null
              }
            ],
            "time":null
          }
        ]
      },
      "claims": {
        "given_name": null,
        "family_name": null,
        "address": {
          "street_address": null,
          "locality": null,
          "country": null
        },
        "::age_100_or_over": null,
        ":age_18_or_over": null
      }
    }
  },
  "transformed_claims": {
    "age_18_or_over": {
      "claim": "birthdate",
      "fn": [
        "years_ago",
        [ "gte", 18 ]
      ]
    }
  }
}
```

この認可リクエストに対して java-oauth-server は次のような認可ページを表示します。

![authorization_endpoint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/3655645f-70a1-87f7-78e7-c9c4e96ff966.png)

最新 IDA 仕様のテスト用アカウントは `inga` です (ログイン ID = `inga`, パスワード = `inga`)。アカウントの検証済みクレームは [document_800_63A.json](https://github.com/authlete/java-oauth-server/blob/master/src/main/resources/ekyc-ida/examples/response/document_800_63A.json) ファイルと [document_UKTDIF.json](https://github.com/authlete/java-oauth-server/blob/master/src/main/resources/ekyc-ida/examples/response/document_UKTDIF.json) ファイルから読み込まれます。これらのファイルは [eKYC-IDA Git レポジトリ](https://bitbucket.org/openid/ekyc-ida/) の [examples/response](https://bitbucket.org/openid/ekyc-ida/src/master/examples/response/) ディレクトリからコピーしたものです。

ログイン ID とパスワードの入力欄に `inga` と `inga` を入れ、Authorize ボタンを押すと、ウェブブラウザは java-oauth-server 上にあるリダイレクションエンドポイントにリダイレクトされます。

リダイレクションエンドポイントの JavaScript は、エンドポイントが受け取ったパラメーター群を表示します。次のような表示になります。

![redirection_endpoint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/52abb8a5-f839-4602-50de-aad998aea97e.png)

発行された ID トークンのペイロード部は次のようになります。データ最小化やフィルタリングが機能していること、変換クレームが `verified_claims/claims` に含まれていることを確認できます。

```json
{
  "iss": "https://authlete.com",
  "sub": "1004",
  "aud": [
    "7681191256"
  ],
  "exp": 1651654999,
  "iat": 1651568599,
  "auth_time": 1651568599,
  "nonce": "n-0S6_WzA2Mj",
  "s_hash": "tEu5XMDHP5FQWevcuhLohQ",
  "email": "inga@example.com",
  "email_verified": false,
  "verified_claims": {
    "verification": {
      "trust_framework": "nist_800_63A",
      "assurance_process": {
        "assurance_details": [
          {
            "assurance_type": "verification",
            "assurance_classification": "strong",
            "evidence_ref": {
              "txn": "v-93jfk284ugjfj2093"
            }
          }
        ]
      },
      "verification_process": "7675D80F-57E0-AB14-9543-26B41FC22",
      "evidence": [
        {
          "type": "document",
          "check_details": [
            {
              "check_method": "pvp",
              "organization": "face_checker",
              "txn": "v-93jfk284ugjfj2093"
            }
          ],
          "time": "2021-06-06T05:33Z"
        }
      ]
    },
    "claims": {
      "given_name": "Inga",
      "family_name": "Silverstone",
      "address": {
        "street_address": "114 Old State Hwy 127",
        "locality": "Shoshone",
        "country": "USA"
      },
      "::age_100_or_over": false,
      ":age_18_or_over": true
    }
  }
}
```

# さいごに

複雑さや不安定さのため、OIDC4IDA 仕様の進化についていくのは大変です。しかし、OAuth/OIDC サーバーのバックエンドシステムとして Authlete を使うことにより、その大変な部分を [Authlete 社](https://www.authlete.com/ja/)のドメイン・エキスパートに任せることができます。

OAuth, OpenID Connect, Financial-grade API, Identity Assurance 及びその他の関連標準仕様の高品質かつ最先端の実装が必要になった際は [Authlete 社にお問い合わせ](https://www.authlete.com/ja/contact/)ください。