oauth-xyz-nodejsの処理の流れを追いかけてみる

XYZと呼ばれる認可プロトコルののリファレンス実装を動かして処理の内容を追いかけてみたのでそのメモです。

**※2020/02時点のリファレンス実装の内容になります。今後実装が変更される可能性がありますし、逆にXYZの仕様が代わっても実装が更新されない可能性もあります。**

## XYZ

XYZとは、トランザクションモデルをベースにした認可プロトコルだそうです。
OAuth 2.0の拡張ではなく、OAuth 2.0の要件を整理して再定義したもの、という位置付けでしょうか。

[XYZ: Transactional Authorization](https://oauth.xyz/)

今回XYZのリファレンス実装を読み解くに当たって、自分は以下の記事を参考にさせていただきました。
こういった情報を発信してくださる方達には感謝しかありません。

* [Transactional Authorization - "XYZ"と呼ばれる認可プロトコルとは - r-weblife](https://ritou.hatenablog.com/entry/2019/07/30/070000)
* [OAuth 3.0? Transactional Authorization - XYZを動かしてみる - Qiita](https://qiita.com/corocn/items/85a84dd76fc1f2b88351)

## 動かしたもの

TypeScript（Node.js + Express）のリファレンス実装を対象にしました。

[securekey/oauth-xyz-nodejs](https://github.com/securekey/oauth-xyz-nodejs)

ClientとAuthZサーバーが用意されていて、Clientはさらにフロントのwebとバックエンドのapiに分かれています。見たところ、webはリクエストで送るパラメータやJWKを外から注入するためのツールとしての機能だけで、実際にClientとしての役割を果たすのはapiだけだと思って良さそうです。

これを動かし、実際に自分が操作した内容がどんなふうに処理されているのかを確認しました。

ちなみに、処理の流れとして大きく「Redirect Transaction」と「Device Transaction」の二つがありますが、今回自分が動かして処理内容を確認したのは「Redirect Transaction」の方だけです。

## 操作内容

docker-composeで動かして`https://localhost:5000`にアクセスすると初期画面が表示されます。
画面下部にある「New Redirect Transaction」をクリックし、続けて「Get Transactions」をクリックします。

![localhost_5000_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490013/efeeb71c-adfd-da2b-496d-ce71b45f1cb7.png)

すると、下にTransactionの情報が表示されます。
ここで表示されたLog Itemの右上の「Poll」をクリックします。

![スクリーンショット 2020-02-28 8.53.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490013/53dd52ac-ce6d-cbb3-fad5-23520fbe0195.png)

更新されたTransactionの情報がさらに下に追加されます。

![スクリーンショット 2020-02-28 8.55.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490013/3e3355d6-d739-ca05-bc46-72f19fed8ca6.png)

次にInteraction URLの先の同意画面で「Approve」をクリックします。

![スクリーンショット 2020-02-28 8.57.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490013/b206a85c-a5a4-b956-745a-174f034c3fa4.png)

コールバックで初期画面に戻るので、再度「Get Transaction」をクリックすると画面にアクセストークンが表示されます。

![スクリーンショット 2020-02-28 8.56.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490013/10f459df-b224-cf5d-4c1e-0eabede30c5d.png)

この一連の操作について処理の流れを追いかけてみました。

## シーケンス図

上記操作のデータの流れに沿って、リファレンス実装のコードを読みながら書いてみたシーケンス図が以下になります。


![oauth-xyz-nodejs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490013/6d96e713-c22c-a437-61b3-7dfef9137046.png)

ClientからAuthZサーバーに送るリクエストの内容などの各種パラメーターは、初期画面に表示されている「Redirect Body」と「Client JWK」を変更せずにそのまま利用しています。

愚直にコードを読んだ結果のメモ程度のものでシーケンス図の書き方も適当なので、かなり見辛いと思いますが、その点はご容赦を…。
もしも誤っている点があれば指摘してもらえると嬉しいです。

## コードを読んでみた雑感

* AS側のエンドポイントがOAuth 2.0と比べてだいぶ少ない。
    * Redirect Transactionだと`/transaction`と`/interact`ぐらいしか使ってない。
*  `/transaction`にアクセスするたびにHandleがクルクルと更新される。
    * ~~Clientが現在のHandleの値を見失ったら最初からやり直し？~~
    * 途中でエラーとか起きたりした場合、エラーレスポンスにはHandleが含まれないので、Clientは新しいTransactionを開始する必要がある。で良いみたい。
* Redirect Transactionの流れを確認するだけだったら「Poll」をクリックするくだりは必要なかったかも。
* ASでもnonceを作成している。Clientが適当なnonceを指定してくることに対するカウンター的な意味合い？
    * Clientは`client_noce`, `server_noce`, `interact_handle`を結合したもののHash値を検証している。
    * Clientが検証しなかったら意味無いのはOIDCと同様？
* ASはTransactionだけではなく、`display`, `user`, `resource`, `keys`にもHandleを設定し保存する。
    * 以降、Clientが`display`, `user`, `resource`, `keys`にHandleの値だけ設定してリクエストしてきてもAS側は処理可能。
    * Transactionが継続している間だけ有効な`OAuth 2.0 Pushed Authorization Requests`をやってるイメージかな。
* このリファレンス実装ではPresentation Types（？）は`jwsd`だけが実装されている。
    * [公式サイトのkeys](https://oauth.xyz/keys/)によるといろいろなタイプがあるみたい。
        * `jwsd`:Detached JSON Web Signature
        * `httpsig`:HTTP Request Signature (Cavage signatures)
        * `dpop`:Demonstration of Proof of Possession
        * `pop`:OAuth Proof of Posession
        * `mtls`:Mutual TLS
* ASは最初のTransactionが始まるときに公開鍵を保存しておき、以降のリクエストはその公開鍵を使ってDetached-JWSを検証をすることで進行中のTransactionにおけるClientの正当性を確認している。
* TokenのRefreshは未実装。
    * ASの`/transaction`のコードに「refresh token?」とコメントがあるのでここで実装される想定？

```javascript:/as/src/routes/routes.ts
      switch (tx.status) {
        case 'authorized':
          ...
        case 'issued':
          // refresh token?
          await tx.save();
          return res.status(403).send({
            message: 'Access token has already been issued for this transaction'
          });
        case 'waiting':
          ...
        case 'denied':
          ...
        case 'new':
          ...
        default:
          return res.status(500);
      }

```

* TokenのRefreshがサポートされるとして、その場合Transactionのステータス管理はどうなるんだろう。
    * `issued`のまま続ける？`issued`の次のステータスが定義される？

---
いつかOAuth 2.0に代わってXYZが利用されるようになる時がくるんでしょうか。そうなったときに置いてけぼりにならないようにもっと勉強しておきたいです。