Identity Assurance - eKYC 時代の OpenID Connect

# はじめに

2018 年 11 月 30 日に『犯罪による収益の移転防止に関する法律』（犯罪収益移転防止法／犯収法）を改正する[命令](https://www.fsa.go.jp/news/30/sonota/20181130/03.pdf)が公表されました。この改正で「[オンラインで完結する自然人の本人特定事項の確認方法の追加](https://www.fsa.go.jp/news/30/sonota/20181130/01.pdf)」が行われ、**eKYC**（electronic Know Your Customer）の根拠法となりました。

そして、当改正から約一年後の 2019 年 11 月 11 日、世界標準仕様策定団体である [OpenID Foundation](https://openid.net/) から『**OpenID Connect for Identity Assurance 1.0**』という技術仕様の[第一版](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID1.html "OpenID Connect for Identity Assurance 1.0 Implementer's Draft 1")が[公開](https://openid.net/2019/11/11/implementers-draft-of-openid-connect-for-identity-assurance-specification-approved/ "Implementer’s Draft of OpenID Connect for Identity Assurance Specification Approved")されました。また、これを受けて同団体内に新たに『[eKYC and Identity Assurance ワーキンググループ](https://openid.net/wg/ekyc-ida/)』が設置されました。それから約半年後の 2020 年 5 月 19 日には、同仕様の[第二版](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html "OpenID Connect for Identity Assurance 1.0 Implementer's Draft 2")が[公開](https://openid.net/2020/05/19/second-implementers-draft-of-openid-connect-for-identity-assurance-specification-approved/ "Second Implementer’s Draft of OpenID Connect for Identity Assurance Specification Approved")されました。

犯収法改正と当技術仕様の策定は独立事象ですが、トラストレームワークの一種として日本の犯収法を表す `jp_aml` という値が同仕様書で定義されるなど（[Trust Frameworks](https://bitbucket.org/openid/ekyc-ida/wiki/identifiers#markdown-header-trust-frameworks)）、関連性が認められます。

本記事では、eKYC 時代に登場した『OpenID Connect for Identity Assurance 1.0』について解説します。（英語版は[こちら](https://medium.com/@darutk/identity-assurance-openid-connect-in-the-ekyc-era-3be78384a1d1)）

**編集**
2020/05/31: 第二版に追随するため、本記事の内容を更新

2022/05/10: 2021 年 11 月に公表された実装者向け草稿第三版で導入された破壊的変更及び次の草稿でも予定されている破壊的変更により、**本記事の内容は古くなっています**。最新仕様に基づいて執筆された『**[実装者による OIDC4IDA 解説](https://qiita.com/TakahikoKawasaki/items/d45d42202d90fc79706a)**』(2022 年 5 月 10 日公開) のほうをご参照ください。


# 1. OpenID Connect のおさらい

[OpenID Connect](https://openid.net/connect/)（以降 OIDC ）の拡張仕様である OpenID Connect for Identity Assurance 1.0（以降 IDA）を理解するのに必要な前提知識のおさらいから始めようと思います。


# 1.1. ID トークンとユーザー情報エンドポイント

OIDC はよく、ユーザー認証に関する技術だと説明されることがありますが、実は OIDC の中心となる『[OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)』では**ユーザー認証の方法は決められておらず、実装依存（仕様の対象範囲外）**とされています。

では OIDC の主目的は何かというと、ユーザー認証の結果やユーザーの属性情報を、**後から検証可能な形で**提供する方法を定めることです。具体的には、ユーザー認証の結果やユーザー属性情報を含む『**ID トークン**』（[2. ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken)）を発行する仕組みを定めました。ID トークンには発行者の署名が含まれているため、ID トークンが保持する情報が正しいことを後から検証することができます。ID トークンの詳細については『[IDトークンが分かれば OpenID Connect が分かる](https://qiita.com/TakahikoKawasaki/items/8f0e422c7edd2d220e06)』を参照してください。

加えて、OIDC は、ユーザー情報を返す API である『**ユーザー情報エンドポイント**』（[5.3. UserInfo Endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo)）を定めました。`openid` スコープを持つアクセストークンを添えてユーザー情報エンドポイントにアクセスすると、ユーザー情報が得られます。


# 1.2. クレーム

ID トークンの形式は **JWT**（[RFC 7519](https://tools.ietf.org/html/rfc7519)）なので、見た目は下記のような意味不明な文字列ですが <sup>※1</sup>、

```
eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlz
cyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4
Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAi
bi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEz
MTEyODA5NzAsCiAibmFtZSI6ICJKYW5lIERvZSIsCiAiZ2l2ZW5fbmFtZSI6
ICJKYW5lIiwKICJmYW1pbHlfbmFtZSI6ICJEb2UiLAogImdlbmRlciI6ICJm
ZW1hbGUiLAogImJpcnRoZGF0ZSI6ICIwMDAwLTEwLTMxIiwKICJlbWFpbCI6
ICJqYW5lZG9lQGV4YW1wbGUuY29tIiwKICJwaWN0dXJlIjogImh0dHA6Ly9l
eGFtcGxlLmNvbS9qYW5lZG9lL21lLmpwZyIKfQ.rHQjEmBqn9Jre0OLykYNn
spA10Qql2rvx4FsD00jwlB0Sym4NzpgvPKsDjn_wMkHxcp6CilPcoKrWHcip
R2iAjzLvDNAReF97zoJqq880ZD1bwY82JDauCXELVR9O6_B0w3K-E7yM2mac
AAgNCUwtik6SjoSUZRcf-O5lygIyLENx882p6MtmwaL1hd6qn5RZOQ0TLrOY
u0532g9Exxcm-ChymrB4xLykpDj3lUivJt63eEGGN6DH5K6o33TcxkIjNrCD
4XB1CKKumZvCedgHHF3IAK4dVEDSUoGlH9z4pP_eWYNXvqQOjGs-rDaQzUHl
6cQQWNiDpWOl_lxXjQEvQ
```

<sup>※1: 実際の ID トークンには改行は含まれませんが、ここでは見やすくするために改行を入れています。</sup>

これをデコードすると、本文には次のような JSON が含まれていることが分かります。

```json
{
  "iss": "http://server.example.com",
  "sub": "248289761001",
  "aud": "s6BhdRkqt3",
  "nonce": "n-0S6_WzA2Mj",
  "exp": 1311281970,
  "iat": 1311280970,
  "name": "Jane Doe",
  "given_name": "Jane",
  "family_name": "Doe",
  "gender": "female",
  "birthdate": "0000-10-31",
  "email": "janedoe@example.com",
  "picture": "http://example.com/janedoe/me.jpg"
}
```

この JSON 内に含まれる各プロパティーは**クレーム**（**claim**）と呼ばれます。

上記の JSON を見ると、

* `iss` この ID トークンの発行者は `http://server.example.com` で、
* `sub` ユーザーの識別子は `248289761001` であり、
* `exp` ID トークンは 2011 年 7 月 21 日頃（`1311281970`）に有効期限をむかえる、

などということが分かります。

特に、`name` 以降のクレームはユーザーの属性に関する情報であることには注目です。

| クレーム | 意味 | 値 |
|:--|:--|:--|
|`name` | 氏名 | `Jane Doe` |
|`given_name` | 名 | `Jane` |
|`family_name` | 姓 | `Doe` |
|`gender` | 性別 | `female` |
|`birthdate` | 誕生日 | `0000-10-31` |
|`email` | メールアドレス | `janedoe@example.com` |
|`picture` | プロフィール画像 | `http://example.com/janedoe/me.jpg` |

これらのユーザー属性情報は基本的には ID トークン発行者が管理しています。しかし、たいていの場合、その情報は ID トークン発行者が運営するサービスにユーザー自身が登録した情報なので、その真偽は定かではありません。そのため、**ID トークンに含まれるユーザー情報を、公的機関が管理する個人情報と同レベルのものとみなすことはできません**。このことが、拡張仕様として IDA を必要とする理由の一つとなっています。


# 1.3. claims パラメーター

クライアントアプリケーションは、ID トークンやユーザー情報エンドポイントからのレスポンスに含めてほしいクレーム群を認可リクエスト時に要求することができます。

簡易的な方法は、事前定義された `profile` や `email` などのスコープを用いて大雑把に要求する方法です（[5.4. Requesting Claims using Scope Values](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims)）。一方、複雑なものの、汎用的でカスタムクレームの要求もできる方法として、**`claims`** パラメーターを使う方法が定義されています（[5.5. Requesting Claims using the "claims" Request Parameter](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter)）。IDA では後者の方法を利用するので、ここでは `claims` パラメーターについて復習します。

|動画による説明はこちら↓|
|:--|
|[OAuth & OIDC 勉強会【認可リクエスト編】](https://www.authlete.com/ja/resources/videos/20200527/) **[#6 クレーム](https://www.youtube.com/watch?v=B4Ws331XAaM&list=PLxDcFnLrbxvZs5eWfxDFdPjrzZ2R7h2Uw&index=7&t=0s)**| 


`claims` パラメーターの値は JSON で、その内容は次のような形式となっています。

```json
{
  "userinfo" : {
    ユーザー情報エンドポイントからのレスポンスに含めてほしいクレーム群
  },
  "id_token" : {
    ID トークンに含めてほしいクレーム群
  }
}
```

下記は [OIDC 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) から抜粋した例です。

```json
{
 "userinfo":
  {
   "given_name": {"essential": true},
   "nickname": null,
   "email": {"essential": true},
   "email_verified": {"essential": true},
   "picture": null,
   "http://example.info/claims/groups": null
  },
 "id_token":
  {
   "auth_time": {"essential": true},
   "acr": {"values": ["urn:mace:incommon:iap:silver"] }
  }
}
```

この例では、ユーザー情報エンドポイントからのレスポンスに `given_name`、`nickname`、`email`、`email_verified`、`picture`、`http://example.info/claims/groups` というクレーム群を、ID トークンに `auth_time` と `acr` というクレーム群を含めてほしいと要求しています <sup>※2</sup>。

<sup>※2: ただし `acr` クレームは特別扱いされます。詳細は [5.5.1.1. Requesting the "acr" Claim](https://openid.net/specs/openid-connect-core-1_0.html#acrSemantics) を参照のこと。</sup>

基本的には、`claims` パラメーターで指定する JSON のトップレベルプロパティーである `userinfo` や `id_token` 内に、要求するクレームを「`"クレーム名":null`」という形で列挙すれば済みます。しかし、細かい条件をつけたい場合、`"クレーム名"` の値として `essential`、`value`、`values` といったプロパティーを含む JSON オブジェクトを指定します。詳細は [OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) の [5.5.1. Individual Claims Requests](https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests) を参照してください。

以上で前提知識のおさらいは終了です。以降、IDA の仕様自体について説明していきます。


# 2. IDA 仕様解説

## 2.1. 実現したい出力

IDA をサポートする OpenID プロバイダは、ID トークンやユーザー情報エンドポイントからのレスポンスに、他のクレームからは独立した **`verified_claims`** という項目を追加します。下記は、仕様書の [6.5.2. UserInfo Response](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html#name-userinfo-response) から抜粋したユーザー情報エンドポイントからのレスポンスの例です。

```json
{
  "sub": "248289761001",
  "email": "janedoe@example.com",
  "email_verified": true,
  "verified_claims": {
    "verification": {
      "trust_framework": "de_aml",
      "time": "2012-04-23T18:25:43Z",
      "verification_process": "f24c6f-6d3f-4ec5-973e-b0d8506f3bc7",
      "evidence": [
        {
          "type": "id_document",
          "method": "pipp",
          "time": "2012-04-22T11:30Z",
          "document": {
            "type": "idcard",
            "issuer": {
              "name": "Stadt Augsburg",
              "country": "DE"
            },
            "number": "53554554",
            "date_of_issuance": "2010-03-23",
            "date_of_expiry": "2020-03-22"
          }
        }
      ]
    },
    "claims": {
      "given_name": "Max",
      "family_name": "Meier",
      "birthdate": "1956-01-28"
    }
  }
}
```

この例において、`email` クレームや `email_verified` クレームは `verified_claims` の外にあるので、従来のクレームです。一方、`given_name`、`family_name`、`birthdate` クレームは　`verified_claims` 内（の `claims` 内）にあり、従来のクレームとは別扱いとなっています。これらの別扱いされているクレームは、**検証済クレーム**（**verified claim**）と呼ばれます。

`verified_claims` の中身を見ていくと、検証済クレームの値は、ドイツ連邦共和国バイエルン州アウクスブルク市が発行した ID カード（番号 = 53554554、発行日 = 2010 年 3 月 23 日、有効期限 = 2020 年 3 月 22 日）に基づくものだということが分かります。加えて、アイデンティティの検証プロセスと保証レベルはドイツの反資金洗浄法（`de_aml` = German Anti-Money Laundering Law）に則っており、**本人確認書類**（**identity document**）の確認は物理的な対面（`pipp` = Physical In-Person Proofing）で行われたことが分かります。

仮に今後、日本の犯収法に則り、遠隔対面処理による eKYC サービスを提供する会社が IDA を実装するとしたら、その OpenID プロバイダが発行する ID トークンやユーザー情報エンドポイントからのレスポンスには、次のような `verified_claims` が含まれることになると思われます。

```json
"verified_claims": {
  "verification": {
    "trust_framework": "jp_aml", // 犯収法
    ......
    "evidence": [
      {
        "type": "id_document",  // 何らかの本人確認書類を使う
        "method": "sripp",      // Supervised remote In-Person Proofing
        ......
      }
    ]
  },
  "claims": {
    ......
  }
}
```


# 2.2. 検証済クレームの要求方法

検証済クレームの要求は、OIDC の `claims` パラメーターで指定する JSON の `userinfo` や `id_token` の直下に `verified_claims` という項目を追加することで行います。下記は IDA の [5.1. Requesting End-User Claims](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html#name-requesting-end-user-claims) に挙げられている最初の例です。

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

この例では、ユーザー情報エンドポイントからのレスポンスに、`given_name`、`family_name`、`birthdate` という検証済クレームを含めることを要求しています。

従来のクレームのように、細かい条件をつけたい場合は、検証済クレーム名の値として `null` のかわりに JSON オブジェクトを指定します。次の例では、[OIDC Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) の [5.5.1. Individual Claims Requests](https://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests) に規定されている方法と同じやり方で、`given_name` と `family_name` に `"essential":true` をつけています。

```json
{  
   "userinfo":{
      "verified_claims":{
         "verification": {
            "trust_framework": null
         },
         "claims":{
            "given_name":{"essential": true},
            "family_name":{"essential": true},
            "birthdate":null
         }
      }
   }
}
```

### 2.2.1. purpose

IDA では、従来の `essential`、`value`、`values` に加え、拡張として `purpose` という項目も追加できるようにしました。これは、各検証済クレーム毎に、クライアントアプリケーションがそれを要求する目的を示すためのものです。次の例では、`given_name` と `birthdate` に `purpose` が付けられています。

```json
{
   "userinfo":{
      "verified_claims":{
         "verification": {
            "trust_framework": null
         },
         "claims":{
            "given_name":{
               "essential":true,
               "purpose":"To make communication look more personal"
            },
            "family_name":{
               "essential":true
            },
            "birthdate":{
               "purpose":"To send you best wishes on your birthday"
            }
         }
      }
   }
}
```

仕様では、`purpose` が存在する場合、その文字列は 3 文字以上 300 文字以下でなければなりません。また、認可サーバーの実装は、それらの `purpose` を同意確認画面に表示しなければなりません。


### 2.2.2. 全ての検証済クレーム

仕様の第一版には、検証済クレーム要求の特別なケースとして、次の例のように `claims` に `null` が指定された場合、「全ての検証済クレームを要求しているとみなす」という規定がありました。

```json
{
   "userinfo":{
      "verified_claims":{
         "claims":null
      }
   }
}
```

第一版の Public Review 期間中、私は「null に特別な意味を持たせないほうがよい」と忠告しましたが　([Issue 1110](https://bitbucket.org/openid/ekyc-ida/issues/1110/identity-assurance-giving-null-and-or "[Identity Assurance] Giving null and/or empty strings special meanings might bring about difficulties in implementations"))、その忠告は取り上げられることなく、第一版は公開されました。

しかし、第一版公開後、ほどなくして「当規定を削除したほうがよいのでは？」（[Issue 1142](https://bitbucket.org/openid/ekyc-ida/issues/1142)）という提案がなされ、結果として第二版から当規定は削除されることになりました。 <sub>(言わんこっちゃない…)</sub>

なお、第二版では、`claims` が null の場合も空 (`{}`) の場合も、そして指定されていない場合も、どれもエラーとなります。


## 2.3. アイデンティティ検証に関する要求事項

IDA では、検証済クレームについてだけではなく、アイデンティティ検証に関しても細かく要求することができます。[5.2. Requesting Verification Data](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html#name-requesting-verification-dat) に挙げられている次の例では、`trust_framework` と `time` を要求しています。

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

仕様書内に挙げられている一番複雑な例は [5.3. Defining constraints on Verification Data](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html#name-defining-further-constraint) にある次のものです。

```json
{
  "userinfo": {
    "verified_claims": {
      "verification": {
        "trust_framework": {
          "value": "de_aml"
        },
        "evidence": [
          {
            "type": {
              "value": "id_document"
            },
            "method": {
              "value": "pipp"
            },
            "document": {
              "type": {
                "values": [
                  "idcard",
                  "passport"
                ]
              }
            }
          }
        ]
      },
      "claims": {
        "given_name": null,
        "family_name": null,
        "birthdate": null
      }
    }
  }
}
```

この例では、ドイツの反資金洗浄法（`de_aml`）に則り、ID カード（`idcard`）またはパスポート（`passport`）を物理的な対面（`pipp`）で確認することによって得られた検証済クレーム群を要求しています。

気付きにくいかもしれませんが、`verified_claims` のネスト構造に対応するため、構文が拡張されています。つまり、末端の要素（`trust_framework` や `method`）に対しては従来の「`essential`、`value`、`values` を含む JSON オブジェクト」を指定しますが、途中の要素（`evidence` や `document`）に対しては、（`null` でないのであれば）出力と同じ階層構造を持たせることになっています。


### 2.3.1. max_age

IDA では、アイデンティティ検証に対する要求内（= `verification` 内）の末端項目について、日付や時刻を表すものについては、従来の `essential`、`value`、`values` に加えて、**`max_age`** も指定できるようにしました。下記は [5.3.2. max_age](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html#name-max_age) に挙げられている例です。

```json
{
  "userinfo": {
    "verified_claims": {
      "verification": {
        "trust_framework": {
          "value": "jp_aml"
        },
        "time": {  
          "max_age": 63113852
        }
      },
      "claims": {
        "given_name": null,
        "family_name": null,
        "birthdate": null
      }
    }
  }
}
```

この例では、検証プロセスの時刻が 63113852 秒（1972 年 1 月 1 日昼頃）より古くない（原文：_"the verification process of the data is not allowed to be older than 63113852 seconds"_）ことを求めています。

仕様は大雑把に日付と時刻に対して `max_age` を指定することができると言っています。しかし、日付や時刻形式を持つデータは下記のように幾つかあり、

- `verification/time`
- `verification/evidence/type=id_document/time`
- `verification/evidence/type=id_document/document/date_of_issuance`
- `verification/evidence/type=id_document/document/date_of_expiry`
- `verification/evidence/type=qes/created_at`
- `verification/evidence/type=utility_bill/date`

これらに対して `max_age` が指定されたときにどう解釈すべきかは、必ずしも単純明快というわけではありません。それぞれの項目について、`max_age` を要求することはそもそも意味的に適切なのか、また、要求された場合はどのように解釈すべきか、について検討する必要があるでしょう。そして、各項目毎の検討結果を仕様に明記すべきだと思います（[Issue 1139](https://bitbucket.org/openid/ekyc-ida/issues/1139)）。


## 2.4. トランザクションの目的

IDA は、新たに `purpose` というリクエストパラメーターを追加しました（[8. Transaction-specific Purpose](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html#name-transaction-specific-purpos)）。これは「2.2.1. purpose」で紹介した検証済クレーム毎の目的を示す purpose とは別の purpose で、ユーザーデータ取得要求全体の目的を表すものです。

なお、`purpose` リクエストパラメーターが指定された場合はそれを同意確認画面に表示しなければならないこと、文字数に 3 〜 300 という範囲制限があることは、検証済クレームの `purpose` と同様です。


## 2.5. 事前定義値

IDA では下表のように、トラストフレームワークや本人確認書類の種類などの識別子を幾つか事前定義しています。これらの値は eKYC-IDA ワーキンググループの Wiki ページ『ekyc-ida / [identifiers](https://bitbucket.org/openid/ekyc-ida/wiki/identifiers)』で確認できます。

| 項目 | 事前定義値 |
|:-:|:--|
|トラストフレームワーク<br/>(trust framework)|<ul><li>`de_aml`<li>`eidas_ial_substantial`<li>`eidas_ial_high`<li>`nist_800_63A_ial_2`<li>`nist_800_63A_ial_3`<li>`jp_aml`<li>`jp_mpiupa`<li>`cz_aml`</ul>|
|本人確認書類<br/>(identity document)|<ul><li>`idcard`<li>`passport`<li>`driving_permit`<li>`de_idcard_foreigners`<li>`de_emergency_idcard`<li>`de_erp`<li>`de_erp_replacement_idcard`<li>`de_idcard_refugees`<li>`de_idcard_apatrids`<li>`de_certificate_of_suspension_of_deportation`<li>`de_permission_to_reside`<li>`de_replacement_idcard`<li>`jp_drivers_license`<li>`jp_residency_card_for_foreigner`<li>`jp_individual_number_card`<li>`jp_permanent_residency_card_for_foreigner`<li>`jp_health_insurance_card`<li>`jp_residency_card`</ul>|
|確認方法<br/>(verification method)|<ul><li>`pipp`<li>`sripp`<li>`eid`<li>`uripp`</ul>|


## 2.6. メタデータ

IDA をサポートする OpenID プロバイダの実装は、次のメタデータを持ちます（[7. OP Metadata](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html#name-op-metadata)）。

|メタデータ|型|説明|
|:--|:--|:--|
|`verified_claims_supported`|真偽値|Identity Assurance をサポートするかどうか|
|`trust_frameworks_supported`|文字列の配列|サポートするトラストフレームワーク群。|
|`evidence_supported`|文字列の配列|サポートする確認根拠 (identity evidence) の種類。|
|`id_documents_supported`|文字列の配列|サポートする本人確認書類 (identity document) の種類。|
|`id_documents_verification_methods_supported`|文字列の配列|サポートする本人確認書類の確認方法 (identity document verification method)。|
|`claims_in_verified_claims_supported`|文字列の配列|サポートする検証済クレーム群|


# 3. IDA 実装

IDA は KYC の方法を定めた技術仕様ではありません。そうではなく、KYC の結果得られた情報をどのように ID トークンやユーザー情報レスポンスに埋め込むかを定めた仕様です。ですので、**IDA を実装したからといって新たに KYC 事業を始められるわけではありません**。むしろその逆で、想定されているのは、**既に KYC をビジネスや業務の一部としておこなっている企業や公的機関が事業拡大やサービス向上を目的として IDA をサポートする**ことです。

IDA を実装済みのソリューションは、eKYC-IDA ワーキンググループの Wiki ページ『ekyc-ida / [implementations](https://bitbucket.org/openid/ekyc-ida/wiki/Implementations)』にリストされています。

この章の残りの部分では、**[Authlete](https://www.authlete.com/)**（オースリート）を用いて IDA を実装する方法について紹介します。なお、IDA は Authlete バージョン 2.2 以降でサポートされます。詳細は Authlete 社まで[お問い合わせ](https://www.authlete.com/ja/contact/)ください。</sup>


## 3.1. 処理フロー

多くの競合製品と異なり、Authlete は OpenID プロバイダそのものではなく、OpenID プロバイダの裏で動く API サーバーです。このため、OpenID プロバイダはお客様自身に実装していただくことになります <sup>※5</sup>。

<sup>※5: OpenID プロバイダのサンプル実装の Java 版 ([java-oauth-server](https://github.com/authlete/java-oauth-server))、C# 版 ([csharp-oauth-server](https://github.com/authlete/csharp-oauth-server))、PHP 版 ([authlete-php-laravel](https://github.com/authlete/authlete-php-laravel))、Python 版 ([django-oauth-server](https://github.com/authlete/django-oauth-server))、Go 版 ([gin-oauth-server](https://github.com/authlete/gin-oauth-server)) はオープンソースで公開されています。</sup>

他の OAuth / OIDC 関連仕様と比べると、IDA に関しては Authlete 側でおこなう作業は比較的軽く、OpenID プロバイダ側の実装作業量のほうが比重が大きくなります。

下図は、IDA を実装するにあたり、OpenID プロバイダと Authlete がそれぞれどのような作業をおこなうかを示しています。

![identity-assurance-with-authlete_japanese.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/0e043a83-ae94-ecd1-6742-26993b6d1a82.png)

上図の処理フローのうち、ポイントになる部分を説明します。

### OpenID プロバイダ : 認可エンドポイント

Authlete を用いて OpenID プロバイダを実装している場合、その OpenID プロバイダの認可エンドポイントの実装は、受け取った認可リクエストの解析・検証処理を Authlete の <code>[/api/auth/authorization](https://docs.authlete.com/#auth-authorization-api)</code> API に委譲します。


### Authlete : /api/auth/authorization API

Authlete の `/api/auth/authorization` API は、認可リクエストに含まれる `"verified_claims"` が仕様に則っているか確認します。ここで誤りが検出された場合、API からのレスポンス（<code>[AuthorizationResponse](https://authlete.github.io/authlete-java-common/com/authlete/common/dto/AuthorizationResponse.html)</code>）には `"action":"BAD_REQUEST"` が含まれます。この結果、OpenID プロバイダ側の実装が正しければ、アプリにはエラーが返されることになります。


### OpenID プロバイダ : 同意確認画面生成

OpenID プロバイダは、`/api/auth/authorization` API が返す情報をもとに、同意確認画面を生成します。当 API が返す情報には、クライアント名や要求されているスコープ群などの情報に加え、IDA に関連のある `purpose` リクエストパラメーターの値や、`claims` リクエストパラメーター内の `"userinfo"` と `"id_token"` の値も含まれます。

「2.4. トランザクションの目的」と「2.2.1. purpose」で説明したように、OpenID プロバイダは、`purpose` リクエストパラメーターの値と検証済クレームごとの `purpose` を、同意確認画面に表示しなければなりません。`purpose` リクエストパラメーターの値は `/api/auth/authorization` API のレスポンスの中から簡単に取り出すことができます。一方、検証済クレームごとの `purpose` を取り出すためには、`userInfoClaims` と `idTokenClaims` に含まれる `"verified_claims"` をパースして JSON の階層を幾つか下がっていく必要があるので、手間がかかります。

`userInfoClaims` と `idTokenClaims` に含まれる `"verified_claims"` の処理を手助けするため、[authlete-java-common](https://github.com/authlete/authlete-java-common) ライブラリには <code>[VerifiedClaimsContainerConstraint](https://authlete.github.io/authlete-java-common/com/authlete/common/assurance/constraint/VerifiedClaimsContainerConstraint.html)</code> クラスが用意されています。このクラスを使うと、検証済クレームの `purpose` を取り出す処理は次のように記述することができます。

```java
// /api/auth/authorization API からのレスポンス
AuthorizationResponse res = ...;

// 認可リクエストの claims パラメーター内の "userinfo"。認可リクエストに claims
// パラメーターが含まれていない場合、また、その JSON 内に "userinfo" が含まれて
// いないかもしくは値が null の場合、userInfoClaims は null になる。
String userInfoClaims = res.getUserInfoClaims();

if (userInfoClaims == null)
{
    // "userinfo" が無いので、これ以上の処理は不要。
    return;
}

// "userinfo" 内に含まれているかもしれない "verified_claims" をパースする。
// なお、"userinfo" 内に "verified_claims" が含まれていなくても変数
// verifiedClaimsConstraint は null にはならない。
VerifiedClaimsConstraint verifiedClaimsConstraint =
        VerifiedClaimsContainerConstraint.fromJson(userInfoClaims)
            .getVerifiedClaims();

// "userinfo" に "verified_claims" が含まれていない、もしくはその値が null の場合
if (!verifiedClaimsConstraint.exists() || verifiedClaimsConstraint.isNull())
{
    // "verified_claims" が無いので、これ以上の処理は不要。
    return;
}

/* 注：第一版に含まれていた下記仕様は第二版で削除されたので、if をコメントアウト。
// "verified_claims" 内に "claims" があってその値が null の場合は特別扱いし、
// 「全ての検証済クレームが要求されている」と解釈する。
if (verifiedClaimsConstraint.isAllClaimsRequested())
{
    // 全ての検証済みクレームが要求されている。この場合、"claims" は null であり、
    // 検証済クレームが "claims" 内に個別に列挙されてはいないので、検証済クレーム
    // ごとの "purpose" を抜き出す処理は不要。
    return;
}
*/

// VerifiedClaimsConstraint.getClaims() は ClaimsConstraint クラスの
// インスタンスを返す。ClaimsConstraint クラスは LinkedHashMap を拡張しているので、
// Map インターフェースに定義されているメソッド群を利用することができる。
ClaimsConstraint claimsConstraint = verifiedClaimsConstraint.getClaims();
for (Map.Entry<String, VerifiedClaimConstraint> entry
     : claimsConstraint.entrySet())
{
    // 要求されている検証済クレームの名前
    String claimName = entry.getKey();

    // "purpose" の値
    String purpose = entry.getValue().getPurpose();

    ......
}
```

なお、上記の処理は（authlete-java-common ライブラリとは別の）[authlete-java-jaxrs](https://github.com/authlete/authlete-java-jaxrs) ライブラリの <code>[AuthorizationPageModel.java](https://github.com/authlete/authlete-java-jaxrs/blob/master/src/main/java/com/authlete/jaxrs/AuthorizationPageModel.java)</code> の中に記述されています。OpenID プロバイダのサンプル実装である [java-oauth-server](https://github.com/authlete/java-oauth-server) はこの <code>[AuthorizationPageModel](http://authlete.github.io/authlete-java-jaxrs/com/authlete/jaxrs/AuthorizationPageModel.html)</code> クラスを用いて同意確認画面を生成しているため、java-oauth-server のソースコードの中を探しても `"verified_claims"` をパースする処理は書かれていないので注意してください。


### OpenID プロバイダ : verified_claims 生成（ID トークン用）

ユーザーから同意を取得したあと、OpenID プロバイダは各種トークンを生成し、それらを含む認可レスポンスをブラウザに返します。Authlete を用いて OpenID プロバイダを実装する場合、トークン群と認可レスポンスの生成をおこなうため、OpenID プロバイダは Authlete の <code>[/api/auth/authorization/issue](https://docs.authlete.com/#auth-authorization-issue-api)</code> API を呼びます。

`/api/auth/authorization/issue` API はオプショナルで `claims` リクエストパラメーターを受け取ります。認可処理の結果 ID トークンが生成される場合（= 認可リクエストの `response_type` に `id_token` が含まれるか、または `response_type` に `code` が含まれ `scope` に `openid` が含まれている場合）、`claims` リクエストパラメーターに指定された JSON は、生成される ID トークンの中に埋め込まれます。

IDA をサポートする OpenID プロバイダは、`"verified_claims"` 及び必要に応じて他の通常クレーム群を含む JSON を用意し、その JSON を `claims` リクエストパラメーターの値として `/api/auth/authorization/issue` API に渡します。

`"verified_claims"` の生成を手助けするため、authlete-java-common ライブラリの `com.authlete.common.assurance` パッケージ下にクラス群が用意されています。他の JSON 生成ライブラリ群との親和性を考慮し、それらのクラス群は `LinkedHashMap` または `ArrayList` のサブクラスとして実装されています。

例えば、次のような JSON を用意したい場合、

```json
{
  "email": "max@example.com",
  "verified_claims": {
    "verification": {
      "trust_framework": "de_aml",
      "evidence": [
        {
          "type": "id_document",
          "method": "pipp",
          "document": {
            "type": "idcard",
            "issuer": {
              "name": "Stadt Augsburg",
              "country": "DE"
            },
            "number": "53554554",
            "date_of_issuance": "2012-04-23",
            "date_of_expiry": "2022-04-22"
          }
        }
      ]
    },
    "claims": {
      "given_name": "Max",
      "family_name": "Meier"
    }
  }
}
```

`com.authlete.common.assurance` パッケージのクラス群を用いると次のように書くことができます。

```java
Map<String, Object> claims = new LinkedHashMap<String, Object>();

// "email": "max@example.com",
claims.put("email", "max@example.com");

// "verified_claims": {
claims.put("verified_claims", new VerifiedClaims()
    // "verification": {
    .setVerification(new Verification()
        // "trust_framework": "de_aml",
        .setTrustFramework("de_aml")
        // "evidence": [ { 
        .addEvidence(
            //     "type": "id_document",
            new IDDocument()
                // "method": "pipp",
                .setMethod("pipp")
                // "document": {
                .setDocument(new Document()
                    // "type": "idcard",
                    .setType("idcard")
                    // "issuer": {
                    .setIssuer(new Issuer()
                        // "name": "Stadt Augsburg",
                        .setName("Stadt Augsburg")
                        // "country": "DE"
                        .setCountry("DE")
                    // }
                    )   
                    // "number": "53554554",
                    .setNumber("53554554")
                    // "date_of_issuance": "2012-04-23",
                    .setDateOfIssuance("2012-04-23")
                    // "date_of_expiry": "2022-04-22"
                    .setDateOfExpiry("2022-04-22")
                // }
                )   
            // }
            )   
        // ] },
        )   
    // "claims": {
    .setClaims(new Claims()
        // "given_name": "Max",
        .putClaim("given_name", "Max")
        // "family_name": "Meier"
        .putClaim("family_name", "Meier")
    // }
    )   
// }
);

// Convert to JSON
Gson   gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(claims);
```

[java-oauth-server](https://github.com/authlete/java-oauth-server) がやっているのと同じように、 `/api/auth/authorization/issue` API を呼び出す際に authlete-java-jaxrs ライブラリの <code>[AuthorizationDecisionHandler](https://authlete.github.io/authlete-java-jaxrs/com/authlete/jaxrs/AuthorizationDecisionHandler.html)</code> を用いる場合、認可リクエストの `claims` パラメーターに `"id_token"` が含まれていると、<code>[AuthorizationDecisionHandlerSpi](https://authlete.github.io/authlete-java-jaxrs/com/authlete/jaxrs/spi/AuthorizationDecisionHandlerSpi.html)</code> インターフェースの <code>[getVerifiedClaims(String, VerifiedClaimsConstraint)](https://authlete.github.io/authlete-java-jaxrs/com/authlete/jaxrs/spi/AuthorizationDecisionHandlerSpi.html#getVerifiedClaims-java.lang.String-com.authlete.common.assurance.constraint.VerifiedClaimsConstraint-)</code> メソッドが呼ばれます。ID トークンに `"verified_claims"` を埋め込みたい場合は、このメソッドの実装から <code>[VerifiedClaims](http://authlete.github.io/authlete-java-common/com/authlete/common/assurance/VerifiedClaims.html)</code> のインスタンスを含む `List` を返すようにしてください。


### OpenID プロバイダ : ユーザー情報エンドポイント

Authlete を用いて OpenID プロバイダを実装している場合、その OpenID プロバイダのユーザー情報エンドポイントの実装 <sup>※6</sup> は、受け取ったリクエストからアクセストークンを取り出し、Authlete の <code>[/api/auth/userinfo](https://docs.authlete.com/#auth-userinfo-api)</code> API に渡します。

<sup>※6: ユーザー情報エンドポイントの実装を含むリソースサーバーのサンプル実装の Java 版 ([java-resource-server](https://github.com/authlete/java-resource-server)、C# 版 ([csharp-resource-server](https://github.com/authlete/csharp-resource-server))、Python 版 ([django-resource-server](https://github.com/authlete/django-oauth-server))、Go 版 ([gin-resource-server](https://github.com/authlete/gin-oauth-server)) はオープンソースで公開されています。PHP 版は [authlete-php-laravel](https://github.com/authlete/authlete-php-laravel) に含まれます。</sup>


### Authlete : /api/auth/userinfo API

Authlete の `/api/auth/userinfo` API は、渡されたアクセストークンを検証します。有効期限切れであったり、スコープに `openid` が含まれていないなど、アクセストークンが不正とみなされる場合、当 API は `"action":"OK"` 以外のレスポンスを返します。アクセストークンが有効の場合は、`"action":"OK"` に加えて、アクセストークンに関する情報も返します。


### OpenID プロバイダ : verified_claims 生成（ユーザー情報レスポンス用）

OpenID プロバイダは、`/api/auth/userinfo` API からのレスポンスに含まれる情報を元に、ユーザー情報エンドポイントのレスポンスに含めるべき情報を用意し、<code>[/api/auth/userinfo/issue](https://docs.authlete.com/#auth-userinfo-issue-api)</code> API を呼びます。

`/api/auth/userinfo` API からのレスポンスに含まれる情報のうち、IDA の文脈で重要なのは `userInfoClaims` です。これは、`/api/auth/authorization` API からのレスポンスに含まれる `userInfoClaims` と同じで、認可リクエストの `claims` パラメーターで指定された JSON に含まれる `"userinfo"` を表しています。

「OpenID プロバイダ : verified_claims 生成（ID トークン用）」のときと同様、OpenID プロバイダは `"verified_claims"` 及び必要に応じて他の通常クレーム群を含む JSON を用意し、その JSON を `claims` リクエストパラメーターの値として `/api/auth/userinfo/issue` API に渡します。

[java-resource-server](https://github.com/authlete/java-resource-server) がやっているのと同じように、`/api/auth/userinfo/issue` API を呼び出す際に authlete-java-jaxrs ライブラリの <code>[UserInfoRequestHandler](https://authlete.github.io/authlete-java-jaxrs/com/authlete/jaxrs/UserInfoRequestHandler.html)</code> を用いる場合、認可リクエストの `claims` パラメーターに `"userinfo"` が含まれていると、<code>[UserInfoRequestHandlerSpi](https://authlete.github.io/authlete-java-jaxrs/com/authlete/jaxrs/spi/UserInfoRequestHandlerSpi.html)</code> インターフェースの <code>[getVerifiedClaims(String, VerifiedClaimsConstraint)](https://authlete.github.io/authlete-java-jaxrs/com/authlete/jaxrs/spi/UserInfoRequestHandlerSpi.html#getVerifiedClaims-java.lang.String-com.authlete.common.assurance.constraint.VerifiedClaimsConstraint-)</code> メソッドが呼ばれます。ユーザー情報エンドポイントからのレスポンスに `"verified_claims"` を埋め込みたい場合は、このメソッドの実装から <code>[VerifiedClaims](http://authlete.github.io/authlete-java-common/com/authlete/common/assurance/VerifiedClaims.html)</code> のインスタンスの `List` を返すようにしてください。


## 3.2. IDA 関連メタデータの設定

IDA をサポートするバージョンの Authlete の Web コンソールでは、IDA 関連のメタデータを編集するための「アイデンティティアシュアランス」タブが提供されます。

|![identity-assurance-metadata.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/8c35a982-d09f-9384-8be5-47a3036c6017.png)|
|:-:|


## 3.3. IDA 実装の実行例

### 3.3.1. 認可画面

次の同意確認画面のスクリーンショットは、

|![identity_assurance_authorization_page.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/de227fa3-d024-b6f6-1ec4-168298bdcb20.png)|
|:-:|

`purpose` リクエストパラメーターの値として `my_purpose`、`claims` リクエストパラメーターの値として下記のものと同内容の JSON を持つ認可リクエストを、OpenID プロバイダのサンプル実装である [java-oauth-server](https://github.com/authlete/java-oauth-server) に渡したときのものです。<sup>※7</sup>

<sup>※7: スクリーンショットおよび下記 JSON は第一版の仕様に基づいて作成したため、`"claims": null` が「全てのクレームを要求」と解釈されていますが、「2.2.2. 全ての検証済クレーム」で説明したとおり、第二版以降は `"claims": null` とするとエラーとなります。</sup>


```json
{
  "id_token": {
    "verified_claims": {
      "claims": null    "注：第二版以降、null はエラーとなる"
    }
  },
  "userinfo": {
    "verified_claims": {
      "claims": {
        "given_name": {
          "essential": true,
          "purpose": "To make communication look more personal"
        },
        "family_name": {
          "essential": true
        },
        "birthdate": {
          "purpose": "To send you best wishes on your birthday"
        }
      }
    }
  }
}
```

同意確認画面の『Identity Assurance』欄にリクエストパラメーター `purpose` の値や検証済クレームの `purpose` が表示されています。


### 3.3.2. ID トークン

次のものは、開発用リダイレクションエンドポイントが返した画面のスクリーンショットです。ID トークン内に `"verified_claims"` が埋め込まれています。

|![identity_assurance_redirection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/0704082a-0eb1-4aae-3d69-d3dc1a590acd.png)|
|:-:|


### 3.3.3. ユーザー情報エンドポイント

次のターミナルのスクリーンショットでは、リソースサーバーのサンプル実装 [java-resource-server](https://github.com/java-resource-server) のユーザー情報エンドポイント（`/api/userinfo`）にリクエストを投げ、`"verified_claims"` を含むレスポンスを取得しています。

|![identity-assurance-userinfo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/f9db9eaa-5885-b9b6-7926-9477d25c8e0c.png)|
|:-:|


### 3.3.4. ディスカバリーエンドポイント

次のものは、OpenID プロバイダのサンプル実装である [java-oauth-server](https://github.com/authlete/java-oauth-server) の `/.well-known/openid-configuration`（参考：OIDC Discovery 1.0 [Section 4.1](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest)）にアクセスした際に表示された内容の一部です。`trust_frameworks_supported` 等の IDA 関連のメタデータが含まれています。

|![identity-assurance-discovery.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/57b02d36-9281-f990-51ad-d618a4a0686d.png)|
|:-:|


# おわりに

『[eKYC and Identity Assurance ワーキンググループ](https://openid.net/wg/ekyc-ida/)』を立ち上げる前に仕様を公開する必要があったため、**OpenID Connect for Identity Assurance 1.0** の[第一版](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID1.html)は拙速に公開されました。そのため、仕様書内には typo も多く、例にも間違いがあり、自動検証用のためにと用意された JSON Schema にも誤りが含まれている状態でした<sup>※</sup>。しかし、その約半年後には数々の修正を含んだ[第二版](https://openid.net/specs/openid-connect-4-identity-assurance-1_0-ID2.html)が公開されました。

<sup>※:[後日談] 仕様作者の [Torsten](https://www.linkedin.com/in/torsten-lodderstedt-37528a1/) から依頼されたので、修正するための[プルリクエスト](https://bitbucket.org/openid/ekyc-ida/pull-requests/1/assurance-fix-typos/diff)を出して、マージされました。</sup>

同ワーキンググループは引き続き活発に活動しているので、ご興味のある方は、[メーリングリスト](http://lists.openid.net/mailman/listinfo/openid-specs-ekyc-ida)や [Issue リスト](https://bitbucket.org/openid/ekyc-ida/issues/)をウォッチされるのがよいと思います。

OpenID Connect for Identity Assurance 1.0 を活用して事業拡大やサービス向上をお考えの方は、是非 [Authlete 社](https://www.authlete.com/)まで[お問い合わせ](https://www.authlete.com/ja/contact/)ください！