3.0? Transactional Authorization - XYZを動かしてみる


趣味で認証認可を学んでいる者です。これは認証認可技術 Advent Calendar 2019 - Qiita 12日目の記事になります。XYZと呼ばれている認可プロトコルのサーバーを動かしてみた話です。

※この情報は2019/12時点の情報です。ドラフトなので変更の可能性は大いにあります。取り扱い注意。※

XYZ Project

OAuth 2.0複雑だしトランザクション中心に整理しようぜーというプロジェクト。それによって提案された認可プロトコルがXYZです。後方互換性なし。

この記事執筆時点ではdraft v3が出てます。

日本語の記事だと以下の記事が分かりやすく整理されてます。感謝。

OAuth 3.0?

IETF106からOAuth 3.0という単語をちらほら見かけるようになりました。そしてOAuth 2 in Actionの著者でもあるJustin Richerから以下のような記事が出ていました。

新しい認可プロトコルの名前がOAuth 3.0なのかXYZなのかTxAuthなのかは決まってませんが、XYZのTransactional Authorizationをベースにしたプロトコルとなりそうです。

XYZの実装

公式で紹介されている実装はいまのところ以下の3つです。

1つ目は Java Spring Boot + Reactフロントエンド。
2つ目は TypeScript + Node.js(Express) + Reactフロントエンド。今回はこれを動かしました。
3つ目はSPA用のクライアントライブラリのようです。

oauth-xyz-nodejs

改めて今回動かしてみたリポジトリはこちら
https://github.com/securekey/oauth-xyz-nodejs

構成は以下のようになってました。WebがReactで作られていたのでSPAのPublic Clientか?と思ったのですが、ASに対してリクエスト投げるClientは別にあるので、ただのデバッグ用のコンソールってことで良いのかなと。

バグがあって起動しなかったのですが既に修正済みで、一手間必要ですが今は問題なく動きます。Justin先生が爆速マージしてくれました。

docker-composeで動かすためには以下をlocalhostからホスト経由に変更。

client/api/src/controllers/routesController.ts
// const asTransactionURL = 'http://localhost:3000/transaction';
const asTransactionURL = 'http://host.docker.internal:3000/transaction';

docker network内でClient - AS間の通信ができないので聞いてみたら「docker-compose upで全部動くように作ってないんやすまんな」とコメントがありました。なのでホスト経由で無理やり通信させるようにして動かします。

こんな感じで色々ゴニョって投げれるようになっています。

Redirect Transaction

「New Redirect Transaction」クリック後「Get Transactions」でハンドルとInteraction URLが取得できます。

Pollを押す度にリクエストが飛んで新しいハンドルがドンドコ生成されます。

Interaction URLの先はOAuthでよく見る同意画面になっていますね。

同意後、再度Get Transactionすると、無事アクセストークンが取得できていました。リフレッシュ等の処理はまだ実装されてないようです。

Client-APIからASへのリクエストログを見てみると、今回はjwsdでリクエストを送っているのでDetached-JWSヘッダが付与されていました。

client-api_1    | {
client-api_1    |   'Content-Type': 'application/json',
client-api_1    |   'Detached-JWS': 'eyJiNjQiOmZhbHNlLCJhbGciOiJSUzI1NiIsImtpZCI6Inh5ei0xIn0..B-C8KbKcuY91Jx3efrsj5YNAwQb7ZhSEQ-pVGmJh9Omuq_x60tDkJryW7TmuZ7HPdbBhoD7XdPFDA_XBjeRAy-aRkEm9uMCW1k08V1LT0u2rpQ2ek0ovm-snYnmq2RsJV_2_A1_KVVW5QQNeFJhLznpZty9pIvMX8-X3HM300d9KARwmFLcMTkSNXFqTA_fgY9rE4zAOl_SirBbS8yt5r-28UNdlhNRHQJre9DFUKigR1prlFNpoMKC-UfJhF6Ca0tGCknbVfEjvSBhnY3tXOj8xNZizj0McPA6TODHCpSqSmsdYYfyMzqu0i_R6mALnuaFu4cupK-6gX5TXwaRJ5Q'
client-api_1    | }

Detached-JWSってなんやねん!と思って調べたのですが、JWTのPayload部分をぶっこ抜いてHTTP Request Bodyで送信し、残りのHeaderとSignatureをヘッダで送信する方式なんですね。上記の例だとダブルドット(..)になっている部分がPayloadを切り取った部分です。詳しくはKeysを参照。

Device Transaction

Device Flowです。認証コードを入力するステップが入ります。若干表示崩れてる。

正しいコードを入力するとRedirect Transaction同様に同意画面に遷移します。後は同じです。

最後にAS側のトランザクションコントローラーを見てみると、トランザクションのステータスを見て処理を分岐してるようでした。

transactionController.ts
      switch (tx.status) {
        case 'authorized':
          tx.status = 'issued';
          tx.access_token = new Handle().toSchema();
          break;
        case 'issued':
          // refresh token?
          await tx.save();
          return res.status(403).send({
            message: 'Access token has already been issued for this transaction'
          });
        case 'waiting':
          await tx.save();
          return res.status(202).json(new TransactionResponse(tx));
        case 'denied':
          await tx.save();
          return res
            .status(403)
            .send({ message: 'User denied approval for this transaction' });
        case 'new':
          if (tx.interact) {
            if (tx.interact.can_redirect) {
              let interact_id = utils.generateRandomString(10);
              let interaction_url =
                'http://localhost:3000/interact/' + interact_id;

              tx.interact.url = interaction_url;
              tx.interact.interact_id = interact_id;

            }

            if (tx.interact.callback) {
              let server_nonce = utils.generateRandomString(20);
              tx.interact.server_nonce = server_nonce;
            }

            if (tx.interact.can_user_code) {
              let user_code = utils.generateUserCode(8);
              let user_code_url = 'http://localhost:3000/interact/device';

              tx.interact.user_code = user_code;
              tx.interact.user_code_url = user_code_url;

            }

            tx.status = 'waiting';

          }
          break;
        default:
          return res.status(500);
      }

まとめ

xyzのnode.js実装を動かしてみました。頭が固いのであんまりしっくりきていません。しばらくは OAuth 2.1を使うことになると思いますが、今後どうなるか楽しみですね。

今回は以上です。明日はBlacpansさんがなにか書いてくれます!楽しみ!