Help us understand the problem. What is going on with this article?

Identity Assurance - eKYC 時代の OpenID Connect

はじめに

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

そして、当改正から約一年後の 2019 年 11 月 11 日、世界標準仕様策定団体である OpenID Foundation から『OpenID Connect for Identity Assurance 1.0』という技術仕様が公開されました。また、これを受けて同団体内に新たに『eKYC and Identity Assurance ワーキンググループ』が設置されました。

犯収法改正と当技術仕様の策定は独立事象ですが、トラストレームワークの一種として日本の犯収法を表す jp_aml という値が同仕様書で定義されるなど(11.1. Trust Frameworks)、関連性が認められます。

本記事では、eKYC 時代に登場した『OpenID Connect for Identity Assurance 1.0』について解説します。(英語版はこちら

1. OpenID Connect のおさらい

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

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

OIDC はよく、ユーザー認証に関する技術だと説明されることがありますが、実は OIDC の中心となる『OpenID Connect Core 1.0』ではユーザー認証の方法は決められておらず、実装依存(仕様の対象範囲外)とされています。

では OIDC の主目的は何かというと、ユーザー認証の結果やユーザーの属性情報を、後から検証可能な形で提供する方法を定めることです。具体的には、ユーザー認証の結果やユーザー属性情報を含む『ID トークン』(2. ID Token)を発行する仕組みを定めました。ID トークンには発行者の署名が含まれているため、ID トークンが保持する情報が正しいことを後から検証することができます。ID トークンの詳細については『IDトークンが分かれば OpenID Connect が分かる』を参照してください。

加えて、OIDC は、ユーザー情報を返す API である『ユーザー情報エンドポイント』(5.3. UserInfo Endpoint)を定めました。openid スコープを持つアクセストークンを添えてユーザー情報エンドポイントにアクセスすると、ユーザー情報が得られます。

1.2. クレーム

ID トークンの形式は JWTRFC 7519)なので、見た目は下記のような意味不明な文字列ですが ※1

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

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

これをデコードすると、本文には次のような 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 トークンやユーザー情報エンドポイントからのレスポンスに含めてほしいクレーム群を認可リクエスト時に要求することができます。

簡易的な方法は、事前定義された profileemail などのスコープを用いて大雑把に要求する方法です(5.4. Requesting Claims using Scope Values)。一方、複雑なものの、汎用的でカスタムクレームの要求もできる方法として、claims パラメーターを使う方法が定義されています(5.5. Requesting Claims using the "claims" Request Parameter)。IDA では後者の方法を利用するので、ここでは claims パラメーターについて復習します。

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

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

下記は OIDC Core 1.0Section 5.5 から抜粋した例です。

{
 "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_namenicknameemailemail_verifiedpicturehttp://example.info/claims/groups というクレーム群を、ID トークンに auth_timeacr というクレーム群を含めてほしいと要求しています ※2

※2: ただし acr クレームは特別扱いされます。詳細は 5.5.1.1. Requesting the "acr" Claim を参照のこと。

基本的には、claims パラメーターで指定する JSON のトップレベルプロパティーである userinfoid_token 内に、要求するクレームを「"クレーム名":null」という形で列挙すれば済みます。しかし、細かい条件をつけたい場合、"クレーム名" の値として essentialvaluevalues といったプロパティーを含む JSON オブジェクトを指定します。詳細は OIDC Core 1.05.5.1. Individual Claims Requests を参照してください。

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

2. IDA 仕様解説

2.1. 実現したい出力

IDA をサポートする OpenID プロバイダは、ID トークンやユーザー情報エンドポイントからのレスポンスに、他のクレームからは独立した verified_claims という項目を追加します。下記は、仕様書の 6.4.2. UserInfo Response から抜粋したユーザー情報エンドポイントからのレスポンスの例です。

{  
   "sub":"248289761001",
   "email":"janedoe@example.com",
   "email_verified":true,
   "verified_claims":{  
      "verification":{  
         "trust_framework":"de_aml",
         "time":"2012-04-23T18:25:43.511+01",
         "verification_process":"676q3636461467647q8498785747q487",
         "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",
         "birthdate":"1956-01-28"
      }
   }
}

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

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

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

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

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

検証済クレームの要求は、OIDC の claims パラメーターで指定する JSON の userinfoid_token の直下に verified_claims という項目を追加することで行います。下記は IDA の 5.1. Requesting End-User Claims に挙げられている最初の例です。

{  
   "userinfo":{
      "verified_claims":{
         "claims":{
            "given_name":null,
            "family_name":null,
            "birthdate":null
         }
      }
   }
}

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

従来のクレームのように、細かい条件をつけたい場合は、検証済クレーム名の値として null のかわりに JSON オブジェクトを指定します。次の例では、OIDC Core 1.05.5.1. Individual Claims Requests に規定されている方法と同じやり方で、given_namefamily_name"essential":true をつけています。

{  
   "userinfo":{
      "verified_claims":{
         "claims":{
            "given_name":{"essential": true},
            "family_name":{"essential": true},
            "birthdate":null
         }
      }
   }
}

2.2.1. purpose

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

{
   "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"
            }
         }
      }
   }
}

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

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

検証済クレーム要求の特別なケースとして、次の例のように claimsnull が指定された場合、「全ての検証済クレームを要求しているとみなす」という規定があります ※3

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

※3: なお、null ではなく空の JSON オブジェクト {} が指定された場合はエラーとする、という規定もあります。

ただし、この規定ですが、「仕様から削除したほうがよいのでは?」という提案が仕様策定者本人から出ているため(Issue 1142)、IDA の将来の改訂版では削除される可能性が高いです。

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

IDA では、検証済クレームについてだけではなく、アイデンティティ検証に関しても細かく要求することができます。5.2. Requesting Verification Data に挙げられている次の例では、timeevidence を要求しています。

{  
   "verified_claims":{
      "verification":{
         "time":null,
         "evidence":null
      },
      "claims":null
   }
}

仕様書内に挙げられている一番複雑な例は 5.3. Defining constraints on Verification Data にある次のものです。

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

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

気付きにくいかもしれませんが、Section 5.3 に次のように書かれているとおり、

This, again, requires an extension to the syntax as defined in Section 5.5. of the OpenID Connect specification [OpenID] due to the nested nature of the verified_claims claim.

verified_claims のネスト構造に対応するため、構文が拡張されています。つまり、末端の要素(trust_frameworkmethod)に対しては従来の「essentialvaluevalues を含む JSON オブジェクト」を指定しますが、途中の要素(evidencedocument)に対しては、(null でないのであれば)出力と同じ階層構造を持たせることになっています。

2.3.1. max_age

IDA では、アイデンティティ検証に対する要求内(= verification 内)の末端項目について、日付や時刻を表すものについては、従来の essentialvaluevalues に加えて、max_age も指定できるようにしました。下記は Section 5.3 に挙げられている例です ※4

{  
   "userinfo":{  
      "verified_claims":{  
         "verification":{  
            "date":{  
               "max_age":63113852
            }
         },
         "claims":null
      }
   }
}

※4: 例では "date" が使われていますが、正しくは "time" だと思われます → Issue 1138

この例では、検証プロセスの時刻が 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)。

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

IDA は、新たに purpose というリクエストパラメーターを追加しました(8. Transaction-specific Purpose)。これは「2.2.1. purpose」で紹介した検証済クレーム毎の目的を示す purpose とは別の purpose で、ユーザーデータ取得要求全体の目的を表すものです。

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

2.5. 事前定義値

IDA では下表のように、トラストフレームワークや本人確認書類の種類などの識別子を幾つか事前定義しています。

項目 セクション 事前定義値
トラストフレームワーク
(trust framework)
Section 11.1
  • de_aml
  • eidas_ial_substantial
  • eidas_ial_high
  • nist_800_63A_ial_2
  • nist_800_63A_ial_3
  • jp_aml
  • jp_mpiupa
確認根拠
(identity evidence)
Section 4.1.1
  • id_document
  • utility_bill
  • qes
本人確認書類
(identity document)
Section 11.2
  • idcard
  • passport
  • driving_permit
  • de_idcard_foreigners
  • de_emergency_idcard
  • de_erp
  • de_erp_replacement_idcard
  • de_idcard_refugees
  • de_idcard_apatrids
  • de_certificate_of_suspension_of_deportation
  • de_permission_to_reside
  • de_replacement_idcard
  • jp_drivers_license
  • jp_residency_card_for_foreigner
  • jp_individual_number_card
  • jp_permanent_residency_card_for_foreigner
  • jp_health_insurance_card
  • jp_residency_card
確認方法
(verification method)
Section 11.3
  • pipp
  • sripp
  • eid
  • uripp

2.6. メタデータ

IDA をサポートする OpenID プロバイダの実装は、次のメタデータを持ちます(7. OP Metadata)。

メタデータ 説明
verified_claims_supported 真偽値 Identity Assurance をサポートするかどうか
trust_frameworks_supported 文字列の配列 サポートするトラストフレームワーク群。事前定義値→ 11.1. Trust Frameworks
evidence_supported 文字列の配列 サポートする確認根拠 (identity evidence) の種類。事前定義値→ 4.1.1. Evidence
id_documents_supported 文字列の配列 サポートする本人確認書類 (identity document) の種類。事前定義値→ 11.2. Identity Documents
id_documents_verification_methods_supported 文字列の配列 サポートする本人確認書類の確認方法 (identity document verification method)。事前定義値→ 11.3. Verification Methods
claims_in_verified_claims_supported 文字列の配列 サポートする検証済クレーム群

3. IDA 実装

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


この章の残りの部分では、Authlete(オースリート)を用いて IDA を実装する方法について紹介します。なお、IDA は Authlete バージョン 2.2 以降でサポートされます。詳細は Authlete 社までお問い合わせください。

3.1. 処理フロー

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

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

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

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

identity-assurance-with-authlete_japanese.png

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

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

Authlete を用いて OpenID プロバイダを実装している場合、その OpenID プロバイダの認可エンドポイントの実装は、受け取った認可リクエストの解析・検証処理を Authlete の /api/auth/authorization API に委譲します。

Authlete : /api/auth/authorization API

Authlete の /api/auth/authorization API は、認可リクエストに含まれる "verified_claims" が仕様に則っているか確認します。ここで誤りが検出された場合、API からのレスポンス(AuthorizationResponse)には "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 を取り出すためには、userInfoClaimsidTokenClaims に含まれる "verified_claims" をパースして JSON の階層を幾つか下がっていく必要があるので、手間がかかります。

userInfoClaimsidTokenClaims に含まれる "verified_claims" の処理を手助けするため、authlete-java-common ライブラリには VerifiedClaimsContainerConstraint クラスが用意されています。このクラスを使うと、検証済クレームの purpose を取り出す処理は次のように記述することができます。

// /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;
}

// "verified_claims" 内に "claims" があってその値が null の場合は特別扱いし、
// 「全ての検証済クレームが要求されている」と解釈する。ただしこの仕様は
// 「2.2.2. 全ての検証済クレーム」で言及したように、将来削除される可能性が高い。
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 ライブラリの AuthorizationPageModel.java の中に記述されています。OpenID プロバイダのサンプル実装である java-oauth-server はこの AuthorizationPageModel クラスを用いて同意確認画面を生成しているため、java-oauth-server のソースコードの中を探しても "verified_claims" をパースする処理は書かれていないので注意してください。

OpenID プロバイダ : verified_claims 生成(ID トークン用)

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

/api/auth/authorization/issue API はオプショナルで claims リクエストパラメーターを受け取ります。認可処理の結果 ID トークンが生成される場合(= 認可リクエストの response_typeid_token が含まれるか、または response_typecode が含まれ scopeopenid が含まれている場合)、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 を用意したい場合、

{
  "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 パッケージのクラス群を用いると次のように書くことができます。

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 がやっているのと同じように、 /api/auth/authorization/issue API を呼び出す際に authlete-java-jaxrs ライブラリの AuthorizationDecisionHandler を用いる場合、認可リクエストの claims パラメーターに "id_token" が含まれていると、AuthorizationDecisionHandlerSpi インターフェースの getVerifiedClaims(String, VerifiedClaimsConstraint) メソッドが呼ばれます。ID トークンに "verified_claims" を埋め込みたい場合は、このメソッドの実装から VerifiedClaims のインスタンスを返すようにしてください。

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

Authlete を用いて OpenID プロバイダを実装している場合、その OpenID プロバイダのユーザー情報エンドポイントの実装 ※6 は、受け取ったリクエストからアクセストークンを取り出し、Authlete の /api/auth/userinfo API に渡します。

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

Authlete : /api/auth/userinfo API

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

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

OpenID プロバイダは、/api/auth/userinfo API からのレスポンスに含まれる情報を元に、ユーザー情報エンドポイントのレスポンスに含めるべき情報を用意し、/api/auth/userinfo/issue 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 がやっているのと同じように、/api/auth/userinfo/issue API を呼び出す際に authlete-java-jaxrs ライブラリの UserInfoRequestHandler を用いる場合、認可リクエストの claims パラメーターに "userinfo" が含まれていると、UserInfoRequestHandlerSpi インターフェースの getVerifiedClaims(String, VerifiedClaimsConstraint) メソッドが呼ばれます。ユーザー情報エンドポイントからのレスポンスに "verified_claims" を埋め込みたい場合は、このメソッドの実装から VerifiedClaims のインスタンスを返すようにしてください。

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

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

identity-assurance-metadata.png

3.3. IDA 実装の実行例

3.3.1. 認可画面

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

identity_assurance_authorization_page.png

purpose リクエストパラメーターの値として my_purposeclaims リクエストパラメーターの値として下記のものと同内容の JSON を持つ認可リクエストを、OpenID プロバイダのサンプル実装である java-oauth-server に渡したときのものです。

{
  "id_token": {
    "verified_claims": {
      "claims": 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

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

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

identity-assurance-userinfo.png

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

次のものは、OpenID プロバイダのサンプル実装である java-oauth-server/.well-known/openid-configuration(参考:OIDC Discovery 1.0 Section 4.1)にアクセスした際に表示された内容の一部です。trust_frameworks_supported 等の IDA 関連のメタデータが含まれています。

identity-assurance-discovery.png

おわりに

eKYC and Identity Assurance ワーキンググループ』を立ち上げる前に仕様を公開する必要があったため、急いで『OpenID Connect for Identity Assurance 1.0』が公開されました。そのため、仕様書内には typo も多く、例にも間違いがあり、自動検証用のためにと用意された JSON Schema(12. JSON Schema)にも誤りが含まれている状態です。そのため、この仕様は引き続き改善していく必要があり、遅かれ早かれ改訂版が出されることになるでしょう。ご興味のある方は、ekyc-ida のメーリングリストIssue リストをウォッチされるのがよいと思います。

OpenID Connect for Identity Assurance 1.0 を活用して事業拡大やサービス向上をお考えの方は、是非 Authlete 社までお問い合わせください!

※:[後日談] 仕様作者の Torsten から依頼されたので、修正するためのプルリクエストを出して、マージされました。eKYC-IDA レポジトリの書込権限まで付与されたけど、それはいらない (^_^; 彼の会社(yes.com)と NDA を結んで以降、何かと巻き込まれがち。

TakahikoKawasaki
株式会社 Authlete の共同創業者。プログラマー兼代表取締役社長。
https://www.authlete.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした