おはようございます、ritouです。
今回はTransactional AuthorizationとしてIETFにDraftが提出されている仕様に注目します。
draft-richer-transactional-authz-02 - Transactional Authorization
⚠これはOAuth 2.0の特定の脆弱性を防いだりするために作られたOAuth 2.0拡張ではありません。まだドラフトなので今後も変わる可能性があります⚠
資料
少し前にプロトコルの紹介をした時の動画もあります。
最近のIETFのお集まりでのプレゼンテーション資料も公開されています。
"XYZ"と名付けられたこのプロトコルについての情報は、以下のサイトにて整理されています。
英語が聞こえて読めて自分で調べる気持ちがあるならこれで十分でしょうが、理解を深めるために中身を見ていきましょう。
XYZとは?
XYZは、トランザクションモデルをベースとした認可プロトコルです。OAuth 2.0の拡張ではありません。 まずは処理の流れをざっくり説明します。ここで出てくる用語の意味はOAuth 2.0と一緒です。
- トランザクション開始要求 : クライアントはバックチャンネルでクライアント自身の情報、対象ユーザーの情報、アクセスするAPIなどのリソース情報、ユーザーインタラクションの情報、鍵情報などを認可サーバーに送信する
- ユーザーインタラクション : ユーザーインタラクションが必要な場合、認可サーバーはリダイレクト先のURLなどをクライアントに返し、クライアントはフロントチャンネルでの処理が行われる
- アクセストークンの発行 : ユーザーインタラクションが不要だったり完了した後、認可サーバーはクライアントにアクセストークンを発行する
ここでのポイントは2点です。
この後紹介しますが、AuthZ Code GrantとImplicit Grantの場合はAuthZ Requestを送って...Client Credentials GrantやROPC Grantの時は直接Token Endpointへ...というように最初からフローが分けて考えられているわけではなく、必ず最初にトランザクション開始要求が送られます。 ユーザー認証やアクセス許可が必要となる場合だけブラウザでリダイレクトなどをしてフロントチャンネルを利用します。
また、いわゆるAuthZ Request/ResponseにはRFC6749で定義されているパラメータに加え、OIDC/PKCE/JARM/TokenBindingなど拡張仕様をトッピングしていくとパラメータが山盛りになり、戻ってきた後のClient側の検証も必要です。 XYZではトランザクション要求に含まれた情報に紐づくシンプルなURLを認可サーバー側が生成してクライアントはユーザーを送ります。 ちょっと前にOAuth 2.0で決済を行いたいときなどのScopeの指定方法についてのブログ記事などもありましたが、フロントチャンネルのクエリパラメータよりもバックチャンネルのJSONデータの方が柔軟な表現ができるだろうという感じです。
この辺りを意識して、OAuth 2.0の各種フローをXYZで表現したらどうなるかを見ていきましょう。
OAuth 2.0 vs XYZ
今回は次の3つがXYZでどのように表現されるかを見ていきます。
- Authorization Code Flow
- Device Flow
- Client Credentials Flow
- Resource Owner's Password Credentials / Assertion Flow
シーケンスをいくつか載せますが、その中で登場人物はOAuthとほぼ一緒です。
- Resource Owner(RO)
- Resource Client(RC)
- Authorization Server(AS)
- Resource Server(RS)
それでは見ていきましょう。
Authorization Code Flow
OAuth 2.0でAuthorization Code Flowは最も有名な認可フローだと思いますが、これをXYZで表現します。
ちょっと小さくて見えないかもしれませんが、
- Transaction Request : RCはOAuth 2.0のAuthZ Requestに含まれるようなパラメータをTransaction Requestとして送信
- Interaction Response : ASはRCに
interaction_url
とhandle
(transaction handle) を返す - User Interaction : RCはROを
interaction_url
に送り、ユーザー認証、アクセス許可を行う - Callback : RCに戻る際にはCSRF対策用
state
とinteract
(interact_handle) の値がついてくる - Transaction continue request : RCは
state
の検証後、handle
(transaction handle),interact_handle
をASに送信 - Access Token : ASはAccess Tokenを返す。ここで
handle
として返される値を用いてRefreshも可能(Transactionは続いていくのだ...)
という感じです。
そんなにシンプルになってる感じではないですが、OAuth 2.0で肥大してしまったAuthZ Requestがバックチャンネルに閉じ込められたようにも見えます。
Device Flow
次はいわゆるDevice Flowです。
もう少しでRFCになりそうですね。こいつをXYZで表現するとこうなります。
- Transaction Request : RCは
interact.type=device
な値を含む Transaction Request を送信 - Interaction Response : ASはRCに
user_code_url
とuser_code
、handle
(transaction handle) を返す - User Interaction : RCはROに別端末で
user_code_url
にアクセスさせ、user_code
を入力した後にユーザー認証、アクセス許可を行う - Polling request : RCは待ってる間、定期的に
handle
(transaction handle) をASに送信する - Access Token : 別端末でのアクセス許可が完了したら、ASはAccess Tokenを返す。ここで
handle
として返される値を用いてRefreshも可能
OAuth 2.0の拡張ではDevice AuthZ Endpointが追加されたりしてましたが、XYZなら Transaction Request を受ける Transaction Endpoint がその辺もやってくれるので、とりあえず AuthZ Code Flow相当のやつと共存できる感じになっています。 仕様的にはもう一つ "user_code" を含まない場合もありますが、Device Flowの仕様で言うところの "Remote Phishing"、OAuth 1.0で言うところの "Session Fixation Attack" あたりのリスクがあるのでTransaction Requestにはもう少し情報が追加される気がします。
ちなみにTransaction Request/Response の値とASの処理をもう少し拡張すればCIBA相当の処理も実現できますね。
Client Credentials Flow
いわゆる 2-legged なフローですが、XYZでも表現できます。
この場合は Transaction Request にuser
, interact
フィールドが含まれず、ASはRCの情報を検証してAccess Tokenを返します。
Resource Owner's Password Credentials / Assertion Flow
いわゆるROPCやある外部IdPからのAssertionを受け取ってAccess Tokenを返す Token Exchange フローについても、XYZで表現できます。
この場合は Transaction Request のuser
フィールドにクレデンシャルや外部IdPのAssertionなどを含み、ASはそれを検証してAccess Tokenを返します。
こんな感じで、OAuth 2.0でサポートされているユースケースについてはXYZでサポートされており、エンドポイントの構成なども統一したものになりそうなことがお分りいただけたかと思います。
基本的な仕様
ここまではイメージしやすいようにOAuth 2.0との比較しながら見てきましたが、細かいところの理解するために必要な基本的な仕様を整理します。
エンドポイント
- Transaction Request/Response
- Transaction continue Request
- Polling Request
など色々出てきましたが、AS側は "Transaction Endpoint" がバックチャンネルにてリクエストを処理し、User Interaction が必要な場合のみフロントチャンネルを使った処理が行われます。
ハンドル
XYZで扱われる情報をASが処理した時に、それらの情報と紐付けたhandle
の値を返すことでその後の処理に利用できます。
- Transaction Handle : Transaction Request からAccess Tokenを返すまでの一連の処理に紐付く
- Client Handle : Client情報に紐付く
- User : ユーザー情報に紐付く
- Interaction : 個々の User Interaction に紐付く
- Resource Handle : アクセス対象となるリソースセットに紐付く
- Key Handle : 鍵情報に紐付く
- Access Token Handle : アクセストークンに紐付く
これがイメージできると理解は楽になるでしょう。
リクエスト
XYZの中で、Transaction Request が一番複雑になり得ます。 XYZのサイト上でここに含むパラメータを色々いじって試すことができます。 盛り盛りにした場合はこちら。
{ // Client の情報 "client": { "name": "My Client Display Name", "uri": "https://example.net/client" }, // User Interaction に関する指定 "interact": { "type": "redirect", "callback": "https://client.example.net/return/123455", "state": "LKLTI25DK82FX4T4QFZC" }, // User に関する指定 "user": { "assertion": "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogImlzcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5NzAsCiAibmFtZSI6ICJKYW5lIERvZSIsCiAiZ2l2ZW5fbmFtZSI6ICJKYW5lIiwKICJmYW1pbHlfbmFtZSI6ICJEb2UiLAogImdlbmRlciI6ICJmZW1hbGUiLAogImJpcnRoZGF0ZSI6ICIwMDAwLTEwLTMxIiwKICJlbWFpbCI6ICJqYW5lZG9lQGV4YW1wbGUuY29tIiwKICJwaWN0dXJlIjogImh0dHA6Ly9leGFtcGxlLmNvbS9qYW5lZG9lL21lLmpwZyIKfQ.rHQjEmBqn9Jre0OLykYNnspA10Qql2rvx4FsD00jwlB0Sym4NzpgvPKsDjn_wMkHxcp6CilPcoKrWHcipR2iAjzLvDNAReF97zoJqq880ZD1bwY82JDauCXELVR9O6_B0w3K-E7yM2macAAgNCUwtik6SjoSUZRcf-O5lygIyLENx882p6MtmwaL1hd6qn5RZOQ0TLrOYu0532g9Exxcm-ChymrB4xLykpDj3lUivJt63eEGGN6DH5K6o33TcxkIjNrCD4XB1CKKumZvCedgHHF3IAK4dVEDSUoGlH9z4pP_eWYNXvqQOjGs-rDaQzUHl6cQQWNiDpWOl_lxXjQEvQ", "type": "oidc_id_token" }, // アクセス対象のリソースについての指定 "resources": [ { "actions": [ "read", "write", "dolphin" ], "locations": [ "https://server.example.net/", "https://resource.local/other" ], "data": [ "metadata" ] } ], // Access Tokenなどにバインドする鍵情報について "key": { "jwks": { "keys": [ { "kty": "RSA", "e": "AQAB", "kid": "xyz-1", "alg": "RS256", "n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8xYJCCNaOKNJn_Oz0YhdHbXTeWO5AoyspDWJbN5w_7bdWDxgpD-y6jnD1u9YhBOCWObNPFvpkTM8LC7SdXGRKx2k8Me2r_GssYlyRpqvpBlY5-ejCywKRBfctRcnhTTGNztbbDBUyDSWmFMVCHe5mXT4cL0BwrZC6S-uu-LAx06aKwQOPwYOGOslK8WPm1yGdkaA1uF_FpS6LS63WYPHi_Ap2B7_8Wbw4ttzbMS_doJvuDagW8A1Ip3fXFAHtRAcKw7rdI4_Xln66hJxFekpdfWdiPQddQ6Y1cK2U3obvUg7w" } ] } } }
これがハンドルを使って最もシンプルにするとこんな感じにもなり得ます。
{ "client": "VBUEOIQA82PBY2ZDJW7Q", "interact": "JMMLJ6393FI7ST9B1SRS", "user": "XUT2MFM1XBIKJKSDU8QM", "resources": [ "dolphin-metadata" ], "key": "7C7C4AZ9KHRS6X63AJAO" }
実装を想像すると処理の分岐など若干めんどくさい気もしなくはないですが、その辺は気にせずに行きましょう。
レスポンス
いくつかレスポンスが定義されています。興味がある方は仕様を見てみてください。
アクセストークン
Transaction Endpointから返されるAccess TokenがBearer トークンの場合はこんな感じで表現されます。
{ "access_token": { "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0", "type": "bearer" } }
Sender-Constrained Token にしたい場合はこんな感じです。
{ "access_token": { "value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0", "jwks": { "keys": [ { "kty": "RSA", "e": "AQAB", "kid": "xyz-1", "alg": "RS256", "n": "kOB5rR4Jv0GMeLaY6_It_r3ORwdf8ci_JtffXyaSx8xYJCCNaOKNJn_Oz0YhdHbXTeWO5AoyspDWJbN5w_7bdWDxgpD-y6jnD1u9YhBOCWObNPFvpkTM8LC7SdXGRKx2k8Me2r_GssYlyRpqvpBlY5-ejCywKRBfctRcnhTTGNztbbDBUyDSWmFMVCHe5mXT4cL0BwrZC6S-uu-LAx06aKwQOPwYOGOslK8WPm1yGdkaA1uF_FpS6LS63WYPHi_Ap2B7_8Wbw4ttzbMS_doJvuDagW8A1Ip3fXFAHtRAcKw7rdI4_Xln66hJxFekpdfWdiPQddQ6Y1cK2U3obvUg7w" } ] } } }
Client 認証
OAuth 2.0の場合は静的/動的なClient認証が済んでいる状態を想定しており、動的な登録(Dynamic Registration)を行う場合は処理が1往復追加されます。 XYZではTransaction RequestにClient情報を含むことで動的な登録も処理を増やさずにできますし、静的な登録の仕組みも使えます。 言い換えるとClient情報を細かく制御できる仕組みとも言えそうですが、まぁ、この辺やりすぎると開発者は混乱しそうです。
まとめ
OAuth 2.0のフロートをXYZで表現するとどうなるかを整理し、現状のXYZサイトに書いてある情報を紹介しました。 OAuth 2.0に求められるユースケース毎の要件を整理し、OAuth 1.0の時のようなバックチャンネルのリクエスト中心のプロトコルとして書き直した感じです。
そう言えば決済アプリなどのクレカ関連の処理でお馴染みの3D Secureでも同じようなフローになっています。
3D Secure 1.0ではXYZのTransaction Request相当のリクエストでカード情報などを送り、ワンタイムパスワードの確認を行うURLにアクセスします。 3D Secure 2.0ではより情報を増やし、リスクベースの判定をしたりします。 Transactionalと言う名前からも、この辺りは共通する部分だったり参考にできる部分はありそうですね。
今後どのような展開を見せるのかはわかりませんが、引き続きウォッチしていきたいと思います。
告知
idcon は今週末開催です。
ここに来る人なら今回の XYZ についてもすぐに理解できるよな! 興味があったら懇親会でお話ししましょう。
ではまた🍎