ラベル OpenID Connect for Identity Assurance の投稿を表示しています。 すべての投稿を表示
ラベル OpenID Connect for Identity Assurance の投稿を表示しています。 すべての投稿を表示

2025年3月30日日曜日

GビズIDの大幅アップデートとOpenID Connect for Identity Assuranceへの対応

こんにちは、富士榮です。

いわゆる法人共通認証基盤と呼ばれる、デジタル庁が提供しているGビズIDの大幅アップデートが公開されましたね。
出典)デジタル庁 - GビズID https://gbiz-id.go.jp/top/


GビズIDについてはこれまでもOpenIDファウンデーションジャパンのイベント等に古くは経産省、デジタル庁へ移管されてからはデジタル庁の方々にお越しいただき技術仕様やトラストフレームワークについてご発表いただいてきました。

OpenID Summit Tokyo 2020 - 2020/1/24

OpenID BizDay #14 - 2021/1/27

OpenID BizDay #15 - 2023/1/10

OpenID BizDay #17 - 2025/2/19

GビズIDについて

簡単に言うと、GビズIDは企業の代表や従業員などが当該の企業に所属していることを表し、例えば補助金の申請などの行政手続きをオンラインで実施することを可能にするためのID基盤ですね。
そのためには当然、当該の企業が実在していること、そしてGビズIDを利用する代表者や従業員が当該企業と適切に関係しており所属していることを保証していくことが重要です。

ここは非常に重要な一方でまだまだ課題も多く、例えば現状は法人の実在性について法務局の発行する印鑑証明書や個人事業主の場合は市町村の発行する印鑑登録証明書を使うことで確認することになりますが、アカウントライフサイクルは各利用企業側に任せるしかないという状況があったりします。


法人共通認証基盤の必要性

この考え方は何も日本だけで必要とされているわけではなく、海外においても同様の要求はあるわけです。OpenID FoundationのeKYC and Identity Assurance Working Groupでは個人の本人確認がどのようにIdentity Providerで実施されたかという情報をRelying Partyへ伝達するためのOpenID Connect for Identity Assurance(最近正式化されましたね!)に加えて、個人が法人とどのような関係性にあるのかを表現するためのAuthority Claims Extensionの開発を進めています。この辺りは日本のOpenIDファウンデーションジャパンのKYC WGの参加メンバーの方々とも協力して国際標準への道筋をうまく作っていきたいところです。

参考)eKYC and Identity Assurance Working Group


GビズIDのアップデート概要

こう言うのは更新履歴を見ていくのが重要ですね。
デジタル庁が公開しているシステム連携ガイドを見ると技術仕様を含め確認ができるので、こちらの更新履歴を見てみましょう。なお、現在「行政サービス向け」のシステム連携ガイドが公開されていますが、そもそも現状のGビズIDは民間サービスとの連携を許可していません。それにもかかわらず行政サービス向け、と明記されているのは今後の民間サービスへの展開を見据えてのことなのかな、、と期待が膨らみますね。

早速更新履歴を見ていきましょう。すでにバージョン2.3なんですね。


結構更新が多いです。さすが大型アップデートです。

個人的に関心が高かったのは、以下の2点です。
  • アカウント種別に管理者(GビズIDメンバー(管理者))が増えた
  • GビズIDトラストフレームワークが策定され、IAL/AALが明記された
アカウント種別はこれまでプライム、メンバー、エントリーの3種類で、原則プライムは法人代表者のためのアカウントでした。そして、メンバーアカウントの作成や管理はプライムの権限者が実施するしかなかったわけですが、いちいち代表者がログインしてアカウント管理をするのか!!という課題も大きかったのだと思います。GビズIDメンバー(管理者)というアカウント管理権限を持ったアカウントを作成することができるようになりました。
ちなみにGビズIDプライムのアカウントはマイナンバーカードを使ったオンライン申請もできるようになってますね。


トラストフレームワークについても別文書で定義されています。
法人共通認証基盤におけるトラストフレームワーク

システム連携ガイドにもざっくりとしたレベル感は記載されていますので、Relying Partyは扱う情報の機密レベルやリスク度合いに応じてどのアカウント種別を要求するか決めていく必要があります。


OpenID Connect for Identity Assuranceへの対応

タイトルにも書いた通り、今回のGビズIDのアップデートの目玉はOpenID Connect for Identity Assurance(OIDC4IDA)への対応です。といっても結論フルスペック対応ではなく、スキーマについてある程度対応した、という程度ではありますが国が提供するサービスに新しい技術仕様が採用されていくのは非常に嬉しいことですね。

具体的にはscopeにjp_gbizid_v1_idaを指定することでOIDC4IDAに対応した属性情報を取得できるようになるようです。

実際に返却される属性(verified_claims)は下記の通りです。
要するにGビズIDのトラストフレームワークに従い、どのような審査・確認が行われたアカウントなのか、という情報がRelying Partyに対して送出されるようになるわけです。

よく見るとauthorityに関する属性も返していますね。この辺りは現在eKYC and Identity Assurance Working Groupで開発を進めているAuthority Claims Extensionを先取りした感じです。

サンプルレスポンスも書いてあります。

組織情報の詳細についても返却できるようになっていますね。

こんな感じで当該組織でそのアカウントがどのような役割を持っているのかが表現できるようになっています。



これはちゃんとこのドキュメントを英訳してグローバルで発信していかないといけませんね。結構先進的なことをやっているので海外の実装者や政府機関にとっても非常に参考になると思います。>デジタル庁さん、がんばってください!













2024年10月13日日曜日

OpenID Connect for Identity Assurance日本語版が公開

こんにちは、富士榮です。


先日もお伝えしたとおり、OpenID Connect for Identity Assurance(通称OIDC4IDA)が正式化されましたが、早くもOpenIDファウンデーションジャパンの有志により日本語化が完了しています。

こちらが日本語版のお知らせ

https://www.openid.or.jp/news/2024/10/openid-connect-for-identity-assurance.html


ぜひ読んでみましょう!

2024年10月4日金曜日

OpenID Connect for Identity Assuranceの仕様が承認されました

こんにちは、富士榮です。

Great newsです。
先日より投票が開始されていたOpenID Connect for Identity Assuranceの仕様が最終化、承認されました。
投票のお知らせ)

最終化に関する公式アナウンス)


今回承認された仕様は以下のとおりです。

皆さん、使っていきましょう。

2024年9月13日金曜日

OpenID Connect for Identity Assurance最終仕様の投票が始まります

こんにちは、富士榮です。



ついに、OpenID Connect for Identity Assuranceの仕様のファイナライズです。
こちらでお知らせした通り、最終の投票に関するアナウンスがありました。

6年くらいやってますからね。ようやく、です。

こちらがオフィシャルの告知です。

以前もお知らせした通り、
9/16 早期投票開始
9/23-30 本投票
というスケジュールで動きます。

ぜひ投票してください。

2024年7月25日木曜日

OpenID Connect for Identity Assuranceの最終版がPublic Review期間に入りました

こんにちは、富士榮です。

ついに、です。
私も(あまり働かない)共同議長をやっているOpenID FoundationのeKYC and Identity Assurance Working Groupの主要スペックである、以下の3つの仕様の最終版がPublic Review期間に入りました。
アナウンスはこちら



仕様編集者の皆さん、本当にお疲れ様でした。

この後のスケジュールですが、
  • レビュー期間:7/24 - 9/22(60日間)
  • 投票のアナウンス:9/9
  • 早期投票のオープン:9/16
  • 最終投票期間:9/23 - 9/30(7日間)

皆さんぜひ仕様を見ていただきコメントをいただければと思います。

2024年5月25日土曜日

TISAがSelect IDを採用、利用者は身元確認方法を選択してサービス利用が可能に

こんにちは、富士榮です。

少し前のニュースとなりますが英国のTISAがSelect IDというサービスを採用したIDスキームの利用を開始したニュースがリリースされました。
TISAはOpenID Connect for Identity Assuranceの仕様開発の初期の段階からOpenID Foundation eKYC and Identity Assurance Working Groupに協力してくれており、ついにサービスローンチまで漕ぎ着けた、ということで感無量です。

参考)TISA(The Investing and Saving Alliance’s)とは
TISA is a not-for-profit membership organisation and a trusted partner of key industry stakeholders in helping shape the future of the UK financial services and the environment in which we operate. We have over 270+ member firms involved in the supply and distribution of savings, investment products and associated services, including the UK’s major investment managers, retail banks, online platforms, insurance companies, pension providers, distributors, building societies, wealth managers, third party administrators, FinTechs, financial consultants, financial advisers, industry infrastructure providers and stockbrokers. ​

TISAは非営利の会員制組織であり、英国の金融サービスの将来と事業環境の形成を支援する上で、主要な業界関係者の信頼できるパートナーである。英国の主要な投資運用会社、リテール銀行、オンライン・プラットフォーム、保険会社、年金プロバイダー、販売会社、ビルディング・ソサエティ、ウェルス・マネージャー、サードパーティ・アドミニストレーター、FinTech、ファイナンシャル・コンサルタント、ファイナンシャル・アドバイザー、業界インフラ・プロバイダー、株式仲買人など、貯蓄・投資商品および関連サービスの供給・流通に携わる270社以上の会員企業が加盟しています。(Deeplによる機械翻訳)


今回のニュースリリースはこちら。
TISA Launches Select ID Scheme – a new trusted and inclusive Digital ID scheme and marketplace with user choice, supported by leading financial and technological institutions
TISAはSelect IDスキーム(主要な金融・技術機関が支援する、信頼性が高く包括的な新しいデジタルIDスキームとマーケットプレイス)をローンチします

リリースによるとSelect IDというIDサービスを使い、ID(KYC)プロバイダをユーザ自身が選択することによる金融サービス等の利用を安全に行うためのスキームを構築しているということです。

参考までにSelect IDはこのサービスです。

こんな感じでユーザが複数のIDプロバイダから利用するものを選択し、サービス(リライングパーティ)を利用できるような仕組みとなっているようです。
この際に、IDプロバイダに求める要件をOpenID Connect for Identity Assuranceを使って提示し、要件を満たした状態でリライングパーティへID情報を連携することができる、という仕組みなんだと思います。




ちなみに、、、SELMID(SElect Multiple IDentities)ってサービスを立ち上げていた時代もありました。同じ思想でサービス設計していました。日本でもこのような考え方が広がると良いですね。





2020年12月17日木曜日

OpenID Connectの脇役たち

本記事は Digital Identity技術勉強会 #iddance Advent Calendar 2020 17日目の記事です。


こんにちは、富士榮です。

今日は年末ということもあり、普段はあまり表舞台に出てこないOpenID ConnectのOptionalなオプションにフォーカスを当ててみようかと思います。
対象とする仕様は「OpenID Connect Core 1.0」とします。


OpenID Connectの設計思想

OpenID ConnectはいわゆるID連携(Federation)を行うための仕様として、OAuth2.0の上位にアイデンティティ層を載せる形で定義されています。

そして、その設計思想は、
  • 簡単なことは簡単に
  • 難しいことも可能に
  • モジュラーデザイン
という原則に則っています。

この中の最初の原則である「簡単なことは簡単に」という部分が普段みなさんが使っているOpenID Connectを使ったログオンの実装の大半で、上記の仕様の第2章「Authentication」に定義されているAuthorization code flow、Implicit flow、Hybrid flowの3つ、特にAuthorization code flowを覚えておけば基本的なWebアプリケーションへのログインのシナリオでは何も難しいことはありません。
※当然、セキュリティ上の考慮事項などはそれなりに存在するため、stateやnonceを正しく使ってcodeを横取りされたりid_tokenを使いまわされることを防いだりする必要はありますので運用環境で使うためにはちゃんと仕様を理解して実装する必要はありますが。。。

また、2つ目の「難しいことも簡単に」、3つ目の「モジュラーデザイン」の原則の通り、OpenID Connect DiscoveryやOpenID Connect Dynamic Client Registrationなど関連仕様との組み合わせでより高度なシナリオにも対応することができるようになっています。

今回はニッチを狙いますので、OPTIONALなパラメータを重点的に見ていきたいと思います。

RFCにおける要求レベルの定義

仕様を読んでいるとMUST、SHOULD、MAY、OPTIONALなど各パラメータ毎に要求レベルが定義されています。
この要求レベル自体はRFC2119で以下の通り定義されています。
※RFC2119の日本語訳はIPAのホームページで公開されています。

以下、IPAの日本語訳です(赤字・太字は筆者)。
  1. 「しなければならない( MUST )」 
    • この語句、もしくは「要求されている( REQUIRED )」および「することになる( SHALL )」は、その規定が当該仕様の絶対的な 要請事項であることを意味します。
  2. 「してはならない( MUST NOT )」
    • この語句、もしくは「することはない( SHALL NOT )」は、その規定が当該仕様の絶対的な禁止事項であることを意味します。
  3. 「する必要がある( SHOULD )」
    • この語句もしくは「推奨される( RECOMMENDED )」という形容表現は、 特定の状況下では、特定の項目を無視する正当な理由が存在するかもしれませんが、 異なる選択をする前に、当該項目の示唆するところを十分に理解し、 慎重に重要性を判断しなければならない、ということを意味します。
  4. 「しないほうがよい( SHOULD NOT )」 
    • この語句もしくは「推奨されない( NOT RECOMENDED )」という形容表現は、 特定の動作が容認できる、ないし、非常に有用である、というような 特定の状況下では、正当な理由が存在するかもしれませんが、 このレベルの動作を実装する前に、当該項目の示唆するところを十分に理解し、慎重に重要性を判断しなければならない、ということを意味します。
  5. 「してもよい( MAY )」 
    • この語句、もしくは「選択できる( OPTIONAL )」という形容表現は、ある要素が、まさに選択的であることを意味します。 その要素を求めている特定の市場があるから、あるいは、 他のベンダーはその要素を提供しないだろうが、その製品機能を拡張する と察知して、その要素を含む選択をするベンダーがあるかもしれません。 特定の選択事項(オプション)を含まない実装は、おそらく機能的には劣る ことになるでしょうが、そのオプションを含む他の実装との相互運用に備えなければなりません( MUST )。 同様に、特定のオプションを含む実装は、そのオプションを含まない実装 との相互運用に備えなければなりません( MUST )。(当然ながら、そのオプションが提供する機能は除かれます。)

まさに今回のターゲットはMAY、もしくはOPTIONALな部分です。

MAYとOPTIONALを仕様から抽出する

早速、仕様の中にどのくらいMAYとOPTIONALがあるのか抽出してみます。
  • MAY:87個
  • OPTIONAL:53個
・・・結構ありますね。

全部カバーしているとキリがないので、独断と偏見でMAY/OPTIONALを5個選んで番付的に紹介してみようと思います。
※もちろんOPTIONALなので実装しているOpenID Providerばかりではありませんので、動かしてみても期待する反応がえられないケースが多いので悪しからず。

十両:prompt

認証リクエストにpromptパラメータを付加することで再認証や同意を求めることができます。

仕様では3.1.2.1のAuthentication Requestの項に定義されています。(OpenIDファウンデーションジャパンの日本語訳から引用)
OPTIONAL. Authorization Server が End-User に再認証および同意を再度要求するかどうか指定するための, スペース区切りの ASCII 文字列のリスト. 以下の値が定義されている.
none
Authorization Server はいかなる認証および同意 UI をも表示してはならない (MUST NOT). End-User が認証済でない場合, Client が要求する Claim 取得に十分な事前同意を取得済でない場合, またはリクエストを処理するために必要な何らかの条件を満たさない場合には, エラーが返される. 典型的なエラーコードは login_requiredinteraction_required であり, その他のコードは Section 3.1.2.6 で定義されている. これは既存の認証と同意の両方, またはいずれかを確認する方法として使用できる.
login
Authorization Server は End-User を再認証するべきである (SHOULD). 再認証が不可能な場合はエラーを返す (MUST). 典型的なエラーコードは login_required である.
consent
Authorization Server は Client にレスポンスを返す前に End-User に同意を要求するべきである (SHOULD). 同意要求が不可能な場合はエラーを返す (MUST). 典型的なエラーコードは consent_required である.
select_account
Authorization Server は End-User にアカウント選択を促すべきである (SHOULD). この prompt 値は, End-User が Authorization Server 上に複数アカウントを持っているとき, 現在のセッションに紐づくアカウントの中から一つを選択することを可能にする. End-User によるアカウント選択が不可能な場合はエラーを返す (MUST). 典型的なエラーコードは account_selection_required である.
prompt パラメータは Client に対して, End-User のセッションがアクティブであることを確認したり, End-User にリクエストに対する注意を促すことを可能にする. none がその他の値とともに用いられる場合はエラーとなる.

アプリの仕様でトランザクションの種類によって強制再認証を求めたり、同意を求めたりするケースもあると思うので、そのような場合はうまくpromptパラメータを使うと良いですね。

前頭:target_link_uri

SAMLでいうrelayStateですね。

仕様にも記載がありますが、全てのログインフローがRelying PartyからOPへのリクエストによって始まるわけではなく、OP起点だったり別のシステム(ポータルなど)から起動されることもあります。そのような場合、ログインフローを起動するシステムはRPのログイン開始エンドポイントへユーザをリダイレクトしてフローを起動するわけですが、ログインが完了した後、必ずしもRPのデフォルトのランディングページに着地させたいわけではなく、別のページへ飛ばしたい、というケースもあります。そのような場合にtarget_link_uriに指定するとログイン完了後に望むページへ遷移させることができます。

ただ、注意点として記載がある通りOpenリダイレクタにならないように指定できるURLはきちんと検証しないといけません。

仕様では4のInitiating Login from a Third Partyの項に定義されています。(OpenIDファウンデーションジャパンの日本語訳から引用)
target_link_uri
OPTIONAL. 認証後, RP がリダイレクトするよう要求された URL. RP は外部サイトへのオープンリダイレクターとして使用されることを防ぐために target_link_uri の値を検証しなければならない(MUST).
SAMLを使ったシステム導入の際はrelayStateに関する要望は結構あったので、今後このパラメータもメジャーになってくるかな?と思います。

関脇:request/request_uri

いわゆるRequest Objectです。FAPI Part2では認可要求自体への署名が要求されるので、普通にQuery Stringに各種リクエストパラメータをくっつけるのではなく、JWTとして認可サーバへ渡してあげる必要があります。また認可要求にJWTそのものを渡すこともできますが、request_uriに指定したエンドポイントを参照させることによりリクエストパラメータを取りに来てもらうこともできます。

仕様では6のPassing Request Parameters as JWTsの項に定義されています。(OpenIDファウンデーションジャパンの日本語訳から引用)
request
OPTIONAL. このパラメータにより, OpenID Connect リクエストを単一の self-contained なパラメータとすることができ, 任意で署名および暗号化を施せるようになる. パラメータ値は Request Object 値である (Section 6.1 参照). Request Object は, 各 Claim がリクエストパラメータとなる JWT である.
request_uri
OPTIONAL. このパラメータにより, 値そのものではなく参照を送ることが可能になる. request_uri 値は https スキーマで始まる URL であり, Request Object 値を含むリソースへの参照となる. 参照先の Request Object は, リクエストパラメータを含む JWT である.
金融サービスに限らず要求についても隠蔽したいケースや署名をつけて確実に渡したいケースではこのパラメータは活躍する場面がありそうです。

大関:id_token_hint

完全に個人的な趣味です。実はAzure ADを使っていると結構活躍の機会が多いんです。例えば今はもう新規のサポートは受け付けていませんがPremium P2の機能のカスタムMFAプロバイダへのセッション引き継ぎはid_token_hintを使って行っていましたし、そもそもOffice365→Azure AD→外部IdPというようなチェーン構成のフェデレーションでは前段で入力されたユーザ名のドメインパートに基づきログイン先のIdPを動的に変更する、なんということもよくやります。そのようなケースでは前段のIdPでのユーザの挙動(入力した値や、その値から導き出された値など)を連鎖する後段のIdPにも伝えないと、ユーザ名を2回入力する必要が出てきたり、とUXを毀損してしまいがちです。

このようなケースではid_token_hintの中に必要なパラメータを詰め込んで後段のIdPへJWTとして渡してあげることで格段にUXが向上します。
(Office365のケースではOpenID Connect→ws-federationの連鎖なのでusernameというパラメータを使っていますが)

仕様では3.1.2.1のAuthentication Requestの項に定義されています。(OpenIDファウンデーションジャパンの日本語訳から引用)
id_token_hint
OPTIONAL. Authorization Server が以前発行した ID Token. Client が認証した End-User の現在もしくは過去のセッションに関するヒントとして利用される. もしこの ID Token に紐づく End-User が認証済, もしくはこのリクエスト中で認証された場合, Authorization Server はポジティブレスポンスを返す. さもなければ, Authorization Server は login_required のようなエラーを返す (SHOULD). prompt=none を利用する場合は, 可能であれば id_token_hint を指定するべきであり (SHOULD), さもなければ invalid_request を返しても良い (MAY). ただしサーバーはその場合可能な限りサクセスレスポンスを返すこと. id_token_hint 利用時は, ID Token の audience に Authorization Server 自身が含まれている必要はない.
id_token_hint として使用するために RP によって受信された OP からの ID Token が暗号化されていた場合, クライアントは暗号化された ID Token を含んだ署名済みの ID Token を復号しなければならない (MUST). クライアントは Authentication Server に送信する署名済みの ID Token をサーバーが ID Token を復号できる鍵を用いて再暗号化し, id_token_hint の値として使用してもよい (MAY).

横綱:claims

最後の横綱級はなんといってもclaimsでしょう。理由はシンプルで私も共同議長を勤めさせていただいるOpenID FoundationのeKYC and Identity Assurance WGで策定中のOpenID Connect for Identity Assuranceの仕様はclaimsがあるから成り立っている、といっても過言ではないからです。

パラメータの意味合いとしてはRPからOPへの認証要求時にclaimsに指定した属性をid_tokenもしくはuserInfoとして提供することを明示的に求める、というものです。OpenID Connect for Identity Assuranceではこの機能を拡張してRPがOPに検証済みの属性を要求する、ということを実現しています。

仕様では5.5のRequesting Claims using the "claims" Request Parameterの項に定義されています。(OpenIDファウンデーションジャパンの日本語訳から引用)
claims
OPTIONAL. 当パラメータは, 特定の Claim の返却を要求するのに用いられる. 値は要求する Claim をリスト化した JSON オブジェクトである.

claims Authentication Request パラメータは, 特定の Claim を UserInfo Endpoint から, かつ/または ID Token 内で, 返却することを要求する. これは要求する Claim のリストを含む JSON オブジェクトとして表される. 要求する Claim のプロパティが指定されていてもよい (MAY).

claims パラメータのサポートは任意である (OPTIONAL). 当パラメータをサポートしない OP に対して RP が当パラメータを使用したとき, OP は 適切と思われるヒューリスティックな方法を用いて, RP と End-User にとって有益と思われる Claim のセットを返すべきである (SHOULD). Discovery で得られる claims_parameter_supported は, OP が当パラメータをサポートしているかどうかを示す.

claims パラメータ値は, OAuth 2.0 要求の中で, UTF-8 でエンコードされた JSON として表される (最終的には OAuth パラメータとして受け渡されるときに form-urlencoded される). Request Object 値として使用される際は, Section 6.1 により, JSON が claims メンバーの値として使用される.

Claim 要求 JSON オブジェクトのトップレベルメンバーは以下の通り:

userinfo
OPTIONAL. UserInfo Endpoint へ返却を要求する個々の Claim のリストを示す. 当メンバーが存在した場合, scope 値で要求された Claim に加え, 当メンバーでリストされた Claim も返却される. 当メンバーが存在しなかった場合, scope 値で要求された Claim のみが返却される.
userinfo メンバーを指定する際は, UserInfo Endpoint を使用するために, response_type に対し, Access Token を Client に発行するタイプの値を指定しなければならない (MUST).
id_token
OPTIONAL. ID Token 内に格納して返却を要求する個々の Claim のリストを示す. 当メンバーが存在した場合, デフォルトの Claim に加え, 当メンバーでリストされた Claim も返却される. 当メンバーが存在しなかった場合, デフォルトの Claim のみが返却される. デフォルトの Claim 等の ID Token の定義については Section 2を, フロー毎の ID Token 要件については Section 3.1.3.63.2.2.103.3.2.113.3.3.6 を参照すること.

上記以外のメンバーが存在してもよい (MAY). 認識できないメンバーが使用された場合は無視しなければならない (MUST).


通常のOPでclaimsを利用している実装ってあまり見かけないのですが、先日某芸能事務所のファンクラブサイトに娘がログインするところをトレースしていたらclaimsを使っているのが判明し驚愕しました(w)
ちなみになぜclaimsを使っているのかはよくわかりません。。。自前RPと自前OPのはずなのでこんなことをしなくてもOPが必要な属性をid_tokenやuserInfoに入れて返してあげれば済むと思うのですが・・・



いかがでしたか?
他にもご紹介したいニッチなパラメータもいっぱいあるので、また機会があればご紹介できればと思います。

2020年5月16日土曜日

Azure ADを拡張してOpenID Connect for Identity Assuranceに対応させる

こんにちは、富士榮です。

先日、OpenID ConnectのID保証に関する拡張仕様である「OpenID Connect for Identity Assurance」について紹介しましたが、実際に仕様を理解するためには実装してみるのが一番、ということでAzure Active Directoryを拡張して実装してみようと思います。(尚、現状は完成している訳ではありません。軽く動作を確認してみた程度です)

Azure ADに足りないもの

OpenID Connect for Identity Assuranceを実装する上で、Azure ADに足りないものは最低限でも以下の通りです。

  • Authorizationエンドポイントがclaimsパラメータを受け付ける機能
  • verified_claims等の属性をid_tokenやuserInfoエンドポイントから返す機能

逆に、Azure ADでも以下について当然ですが出来ます。

  • 認証
  • ユーザデータを保持するデータベース
  • アプリケーション(Client)管理

実装に向けた戦略

ということで、以下の戦略で拡張してみることにしました。
  • Azure ADのエンドポイントをラップするWebアプリを用意し、request/responseの整形を行う
  • 認証、データ保持、アプリケーション管理はAzure ADの機能を使う
  • OpenID Connect for Identity Assuranceで拡張された属性(verified_claims関係)はAzure ADのスキーマを拡張する
こんな感じの仕組みになるはずです。


いざ実装

準備1:Azure ADのスキーマを拡張する

Azure ADにはスキーマ拡張を行う機能がありますので、今回はこの機能を使ってAzure AD上にID保証に関する情報、ID保証済みの属性を保存できるようにします。

Azure ADのスキーマ拡張に関するドキュメントはこちら

OpenID Connect for Identity Assuranceでは「verified_claims」というエレメントの定義がされており、配下に「verification」と「claims」というエレメントが存在します。
  • verification : IDの検証をどのように行ったのか?の記録
    • trust_framework : どんなルールによってIDの確認・検証をしたのか。例えば犯罪収益移転防止法や携帯電話不正利用法などの、本人確認を行った際の根拠法やルールを指定します
    • Evidence : ID確認時に利用した証拠(エビデンス)を指定します。免許証などですね。
      • type : エビデンスの種類を指定します。id_document(証明書類)、utility_bill(公共料金の領収書)、qes(欧州限定ですがeIDASの電子証明書)がありますが、ここではid_documentを使いました。
      • method : typeで指定したエビデンスをどのように確認したのか?を指定します。例えば対面で確認した場合は「Physical In-Person Proofing」ということで「pipp」という値を設定します。
      • document : typeにid_documentを指定した場合に、具体的に何の証明書類を使ったのか?を指定します。例えば日本の免許証なら「jp_drivers_license」を指定します。
      • issuer : 証明書を発行した機関に関する情報を指定します。
        • name : 機関名
        • country : 機関の属する国
      • date_of_issuance : 証明書類の発行日を指定します。
      • date_of_expiry : 証明書の有効期限を指定します。
  • claims : 検証された属性(ここは要求された属性を普通に指定します)
    • given_name : 検証済みの名
    • family_name : 検証済みの姓
    • など

この形でAzure ADの属性を拡張出来ればいいのですが、上記ドキュメントにもある通り、拡張できる属性の型はStringやInteger、DateTimeなどに限定され入れ子の構造や配列構造が取れません。本来なら複数のエビデンスを使ったID証明などもあり得ますが、現状はデータをフラットで持つしかありませんので諦めます。

具体的な方法は以下の通りです。
  • Directory.AccessAsUser.Allをスコープに指定してaccess_tokenを取得する
  • https://graph.microsoft.com/v1.0/schemaExtensionsに拡張したいスキーマの構造(JSON)をPOSTする
  • 成功したら他のクライアントからでも参照可能な様にスキーマをアクティベーションする(status属性をPATCHでActiveに更新する)
今回、以下の2つの拡張属性を作りました。
  • verification
  • verifiedClaims

それぞれのPOSTしたデータはこちらです。
- verification
{
    "id":"verification",
    "description": "verification extension for OpenID Connect for Identity Assurance",
    "targetTypes": [
        "User"
    ],
    "properties": [
        {
            "name": "verificationId",
            "type": "String"
        },
        {
            "name": "trustframework",
            "type": "String"
        },
        {
            "name": "evidenceType",
            "type": "String"
        },
        {
            "name": "evidenceMethod",
            "type": "String"
        },
        {
            "name": "evidenceDocumentType",
            "type": "String"
        },
        {
            "name": "evidenceDocumentIssuerName",
            "type": "String"
        },
        {
            "name": "evidenceDocumentIssuerCountry",
            "type": "String"
        },
        {
            "name": "evidenceNumber",
            "type": "String"
        },
        {
            "name": "evidenceDateOfIssurance",
            "type": "DateTime"
        },
        {
            "name": "evidenceDateOfExpiry",
            "type": "DateTime"
        }
    ]
}



- verifiedClaims
{
    "id": "verifiedClaims",
    "description": "verified claims extension for OpenID Connect for Identity Assurance",
    "targetTypes": [
        "User"
    ],
    "properties": [
        {
            "name": "verificationId",
            "type": "String"
        },
        {
            "name": "givenName",
            "type": "String"
        },
        {
            "name": "familyName",
            "type": "String"
        }
    ]
}


ちなみに、拡張スキーマの単位でid(属性名となります)をつけることが出来ますが、.net、.comなど限られたTLDでカスタムドメインをAzure ADに追加している場合は、[カスタムドメイン名]_[拡張属性名]という名前で属性を作ることが出来ますが、カスタムドメインを持っていない場合は「ext][8桁のランダム文字列]_[拡張属性名]という形で属性名が払い出されます。

当然ですが、この拡張属性に値を入れた状態のユーザを用意しておく必要があります。この辺りはGraph APIを使って値のセットをしてください。

準備2:Azure ADをラップするWebサービスを作る

ココが本番です。といってもやることはシンプルです。
(本来はちゃんと実装しないと危険ですので注意してください。本ポストにサンプルコードが出てきますが、かなり手抜きなのであくまで参考として捉えてください


  • Authorizationエンドポイント
    • clientからの要求の中でAzure ADが受け取ってくれないclaimsパラメータをパースして保持します。後からレスポンスを作るときに何が要求されていたのかを判断するために使います。
    • ※テスト実装では決めうち実装なので実際には保持していません
router.get('/authorize', (req, res) => {
    // get claims from request
    var _claims = req.query.claims;
    // redirect to Azure AD
    var target = lib.addQueryTo(conf.AAD_AuthZ, {
            response_type: 'code',
            scope: 'openid User.Read',
            client_id: req.query.client_id,
            state: req.query.state,
            redirect_uri: req.query.redirect_uri });
    res.redirect(target);
});


  • Tokenエンドポイント
    • codeを受け取ってAzure ADのTokenエンドポイントへ渡してaccess_token、id_tokenを受け取ります。
    • id_tokenには当然verified_claimsが含まれていないので、一度id_tokenをほどき、必要に応じてaccess_tokenを使ってMicrosoft Graphで追加の属性を取得、id_tokenを生成してclientへ返却します。
    • ※テスト実装ではPKCEに対応させていませんので、code横取りをして実装しています。本来はclientとPKCEに使う情報を共有しないと横取り出来ないので、ここはちゃんと考えないとダメです。
router.post('/token', async (req,res) => {
    try{
        let response_from_aad_token = await request({
            url: conf.AAD_Token,
            method: "POST",
            form: {
                grant_type: 'authorization_code',
                code: req.body.code,
                client_id: req.body.client_id,
                client_secret: req.body.client_secret,
                redirect_uri: req.body.redirect_uri
            },
            json: true
        })
        console.log(response_from_aad_token);
        // extract response and re-generate id_token with verified claims
        let decoded_jwt = jwt.decode(response_from_aad_token.id_token)

        // get additional claims using graph api
        let response_from_graph = await request({
            url: conf.AAD_Graph + "/v1.0/me?$select=id,userPrincipalName," + conf.AAD_ExtPrefix + "_verifiedClaims," + conf.AAD_ExtPrefix + "_verification",
            method: "GET",
            headers: {
                'Authorization': 'Bearer ' + response_from_aad_token.access_token,
                'Content-Type': 'application/json'
            }
        })
        console.log(response_from_graph);
        var user = JSON.parse(response_from_graph);
        // get properties from user object
        for (var prop in user){
            if(prop.includes('_verification')){
              var _verification = {
                trustframework: user[prop].trustframework,
                evidenceType: user[prop].evidenceType,
                evidenceMethod: user[prop].evidenceMethod,
                evidenceDocumentType: user[prop].evidenceDocumentType,
                evidenceDocumentIssuerName: user[prop].evidenceDocumentIssuerName,
                evidenceDocumentIssuerCountry: user[prop].evidenceDocumentIssuerCountry,
                evidenceDateOfIssurance: user[prop].evidenceDateOfIssurance,
                evidenceDateOfExpiry: user[prop].evidenceDateOfExpiry
              }
            } else if(prop.includes('_verifiedClaims')){
              var _verifiedClaims = {
                familyName: user[prop].familyName,
                givenName: user[prop].givenName
              }
            }
        }
        // create new jwt.
        var privateKey = fs.readFileSync('./private_key.pem', 'utf-8')
        var new_jwt = null;
        if (typeof _verification !== 'undefined') {
            new_jwt = jwt.sign({
                sub: decoded_jwt.sub,
                iss: 'https://' + req.headers.host,
                aud: decoded_jwt.aud,
                iat: decoded_jwt.iat,
                exp: decoded_jwt.exp,
                email: decoded_jwt.email,
                verified_claims: {
                    verification: {
                        trust_framework: _verification.trustframework,
                        evidence: [
                            {
                                type: _verification.evidenceType,
                                method: _verification.evidenceMethod,
                                document: {
 type: _verification.evidenceDocumentType,
 issuer: {
name: _verification.evidenceDocumentIssuerName,
country: _verification.evidenceDocumentIssuerCountry
 },
 number: _verification.evidenceNumber,
 date_of_issuance: _verification.evidenceDateOfIssurance,
 date_of_expiry: _verification.evidenceDateOfExpiry
                                }
                            }
                        ]
                    },
                    claims: {
                        given_name: _verifiedClaims.givenName,
                        first_name: _verifiedClaims.familyName
                    }
                }
            }, privateKey, { algorithm: 'RS256' });    
        } else {
            new_jwt = jwt.sign({
                sub: decoded_jwt.sub,
                iss: 'https://' + req.headers.host,
                aud: decoded_jwt.aud,
                iat: decoded_jwt.iat,
                exp: decoded_jwt.exp,
                email: decoded_jwt.email,
            }, privateKey, { algorithm: 'RS256' });    
        }
        
        res.send({
            token_type: "bearer",
            scope: "openid",
            expires_in: response_from_aad_token.expires_in,
            access_token: response_from_aad_token.access_token,
            id_token: new_jwt
        });
    } catch(e){
        console.log(e);
    }
});


  • userInfoエンドポイント
    • access_tokenを使ってMicrosoft Graphから必要な属性を取得して、整形した上でclientへ返却します
    • ※テスト実装ではここは実装していません。id_tokenにverified_claimsを入れて返却するところまでです。
  • その他
    • .well-known/openid-configurationとかjwks_uriは必要に応じて実装します。id_tokenの署名をAzure ADではなくこのWebサービス側で行うので対応した公開鍵などの情報をclientへ公開してあげる必要があります。

とりあえずこんな感じでnode.jsで実装してみています。


実際に動かす

テストするにしてもclientが必要になるので、phpでちょこっと書いておく必要があります。
やるべきことはclaimsパラメータを認証要求に乗せて検証済み属性を要求する、ということだけであとは普通のOpenID Connectのクライアントです。

こんな感じですね。
    // claims生成
    $verificationArray = array(
        'trust_framework'=>'null'
    );
    $claimsArray = array(
        'given_name'=>'null',
        'family_name'=>'null'
    );
    $verified_claimsArray = array(
        'verification'=>$verificationArray,
        'claims'=>$claimsArray
    );
    $id_tokenArray = array(
        'email'=>'null',
        'verified_claims'=>$verified_claimsArray
    );
    $claimsArray = array(
        'id_token'=>$id_tokenArray
    );
    
    // GETパラメータ関係
    $query = http_build_query(array(
        'client_id'=>$client_id,
        'response_type'=>$response_type,
        'redirect_uri'=> $redirect_uri,
        'scope'=>'openid User.Read',
        'state'=>$state,
        'nonce'=>$nonce,
        'claims'=>json_encode($claimsArray)
    ));
    // リクエスト
    header('Location: ' . $authorization_endpoint . '?' . $query );


動かすとこんな感じになります。
node.jsを動かすのにglitchを使っているので起き上がるまでにちょっと時間がかかってますが。。。


ということで、ここまでのソースはこちらにあります。
くれぐれも真似して使わない様にしてください。色々危ないので。
https://github.com/fujie/aad_oidc4ida