SkyWay ConfにFirebaseでの認証を組み込んでみた

最近 [SkyWay](https://webrtc.ecl.ntt.com/) のデモアプリ実装である [SkyWay Conference](https://conf.webrtc.ecl.ntt.com/) がOSSとして公開されました。

ref: https://qiita.com/yusuke84/items/e86501810acaa146195d （以下解説記事と呼称）

使い始めるまでのやり方が丁寧に書かれているので、試してみたい方は必読です。

さて解説記事の[FAQ](https://qiita.com/yusuke84/items/e86501810acaa146195d#q-%E7%AC%AC%E4%B8%89%E8%80%85%E3%81%8C%E5%8B%9D%E6%89%8B%E3%81%AB%E4%BC%9A%E8%AD%B0%E3%81%AB%E5%8F%82%E5%8A%A0%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%82%92%E9%98%B2%E3%81%90%E3%81%93%E3%81%A8%E3%81%8C%E5%87%BA%E6%9D%A5%E3%81%BE%E3%81%99%E3%81%8B)にもあるように

> Q. 第三者が勝手に会議に参加することを防ぐことが出来ますか？
>
> SkyWay Confとしては、RoomIDがわかれば参加することが出来てしまいます。対策としては、RoomIDを複雑なものにするか、SkyWay ConfのホスティングサーバにBasic認証やIPアドレス制限をかける等、外部からアクセスされないような対策をとって下さい。

Room名が分かっている状態で同一のホスティングページにアクセスできれば誰でも会議に参加することが出来ます。
これは手軽さというメリットとも言えますが、実際のサービスに導入する際はセキュリティの面も考慮する必要が出てくるため何かしらの対策が必要です（いわゆるZoom爆撃みたいなことに対処するやつとか。）

そこで今回は解説記事中でも触れられていた「APIキー認証」とFirebaseの各種サービスを組み合わせて上記の課題について取り組んでみようと思います。

変更箇所などサンプルコードは当記事にはあまり載せず、実際の実装はGitHubのコードへのリンクからたどってみてください。


## 事前準備

解説記事に書かれていることは概ね終わっていて手元での動作が確認できているようにしてください。

本記事を読むのが面倒な方は今回私が変更した箇所をざっと眺めて頑張ってみてください。

https://github.com/yamachu/skyway-peer-authentication-samples/compare/master...misc/firebase-auth
https://github.com/yamachu/skyway-conf/compare/master...misc/feat-auth

## APIキー認証

SkyWayにはAPIキー認証の機能が備わっています。
Peerの[生成時](https://github.com/skyway/skyway-conf/blob/5e8314ce0cdb8dacf9f54e75747714e441b06bc5/src/conf/utils/skyway.ts#L5-L11)に自前の認証サーバーから払い出されたCredentialをパラメータとして付与することで当該の機能が利用できます。

https://github.com/skyway/skyway-peer-authentication-samples/blob/master/README.jp.md

上記のドキュメントを見ると

> \$timestamp:\$ttl:\$peerIdの文字列にAppのsecretKeyを秘密鍵として利用して、HMAC-SHA256 アルゴリズムを利用して計算します。

とあるのでそれをちゃちゃっと実装してしまえば完了なのですが、リポジトリには様々な言語での実装が含まれているので、今回はこちらを使っていきます。

丁寧に `checkSessionToken` という関数があり、コメントで

> // Implement checking whether the session is valid or not. 
 // Resolve if the session token is valid. 
 // Reject if it is invalid.

とあるのでこれに沿ってその関数内でユーザの認証を行います。

自分が行った実装では、ユーザから送られてきたPeerIdから導出されるユーザのUidとIdTokenから導出されるUidの検証し、後述しますが、PeerIdから導出されるroomIdとあらかじめ作成したroomIdが一致するレコードがあるかどうかでCredentialの払い出しを行ってよいかどうかを判断しています。

認証の他に認可も同時に行っているので少しわかりづらくなってはいますが、そんなやり方もあるよねと見逃してください。

https://github.com/yamachu/skyway-peer-authentication-samples/commit/e817fe5ae8c694da19d2d67279d2adee592ae3bc

これで部屋を持っている本人からのリクエストに対してCredentialを発行できるようになりました。

## Roomの作成

話が前後してしまいましたが、ここではRoomの作成について説明します。
今回の実装ではroomIdに対して参加できるユーザを紐付ける形で実装を行いました。
そのためチャットに参加する前にその紐付けが出来ている必要があります。

元々のSkyWay Confの作りは、部屋の作成 == 部屋へのJoinのような作りになっています。
https://github.com/skyway/skyway-conf/blob/master/src/index/app.tsx#L8-L12

ここを書き換えて部屋の作成を行ってみます。
今回DBにFirebase Firestoreを使っているのでユーザ側からWriteする形にも出来ますが、ユーザは限られたReadのみ出来る形の方が考えることが少なくていいので、クライアントからAPIに対して部屋作成を委譲するやり方で作ります。
FirestoreのRuleに関して本記事では説明しませんが、適切に処理しておいてください。

まずはAPI側から作りましょう。
部屋の作成は先程作成した認証APIを含んだアプリケーションに書き足す形で作ってみます。

https://github.com/yamachu/skyway-peer-authentication-samples/commit/608bf7c5c825e4c4851022de965dd25323144a67

誰とチャットを行うかといったパラメータを受け取り、API側でランダムに部屋番号を生成し、リクエストを行ったユーザとチャットを行う相手にレコードを生やしています。
ユーザは今回生えた自分に紐付けられたレコードを元に部屋への参加処理などを行います。

このAPIを呼び出すクライアント側の変更は

https://github.com/yamachu/skyway-conf/commit/f1ad74dea48988c525c082b6f7db9ce5ee88e7e0

API側で必要になるパラメータをまとめてJsonで投げつけるだけの素朴な実装になっています。
複数人数でのチャットにも対応したいなどがあればAPI側の実装やクライアントから送るパラメータを書き換えれば対応も出来るでしょう。

これでユーザとRoomの紐付けが完了しました。
あとは前セクションで作成したAPIキー認証のAPIを叩くだけです。

## Roomへの参加

それでは今までに作ってきたAPIなどを使ってRoomへの参加処理を書いていきましょう。
APIキー認証により取得できたCredentialの使い方などはAPIキー認証のドキュメントに記載されています。

```js
$.post('http://localhost:8080/authenticate',
  {
    peerId: 'TestPeerID',
    sessionToken: '4CXS0f19nvMJBYK05o3toTWtZF5Lfd2t6Ikr2lID'
  }, function(credential) {
    var peer = new Peer('TestPeerID', {
      key: apikey,
      credential: credential
    });

    peer.on('open', function() {
      // ...
    });
  }).fail(function() {
    alert('Peer Authentication Failed');
  });
```

https://github.com/skyway/skyway-peer-authentication-samples/blob/master/README.jp.md#%E4%BE%8Bjavascript%E3%81%A8jquery%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88 より引用

この処理を行っているSkyWay Confのソースの箇所は

https://github.com/skyway/skyway-conf/blob/5e8314ce0cdb8dacf9f54e75747714e441b06bc5/src/conf/utils/skyway.ts#L3-L20

こちらになります。
後々の拡張なども考え、この `initPeer` 内でauthenticateAPIを叩くのではなくパラメータとしてcredentialを渡す形で実装していきます。

https://github.com/yamachu/skyway-conf/commit/a0e75cefbf5e041761bcb02fc30ea024a3534a14

PeerIdを元にauthenticateAPIはCredentialを発行するため、PeerIdも渡せるように変更します。
今回はこのPeerIdはユーザのuidとroomIdを組み合わせた形式で実装しました。
PeerIdは文字列ならなんでも良いわけではなく、

https://github.com/skyway/skyway-js-sdk/blob/b0606ad6e95d2fe3901525eb85b5fd8653183129/src/shared/util.js#L3-L11

こんな形式である必要があるのでこれに留意して生成します（このformatである必要があることを知らず接続できずに少し悩みました）。

あとはこの `initPeer` メソッドにcredentialを渡すためにAPIキー認証APIにリクエストを送る箇所を書いてつなぎこみます。

https://github.com/yamachu/skyway-conf/commit/7cd017c86ca3b820c66bd4238c2a23e433782c39

これでAPIキー認証を使ってroomへ参加することが可能となりました。

## 終わりに

詳細の説明はほとんどしないで、とりあえずこのあたりを押さえておけばAPIキー認証出来るってことを紹介しました。

記事中では紹介しませんでしたが、今回の実装では部屋を作ることが出来る権限（FirebaseのCustom Claim）を持っているユーザのみが部屋を作ることが出来る作りになっています。
これを試すには
https://github.com/yamachu/skyway-peer-authentication-samples/blob/c7ebd3e4a3ea3ad918b0efed2f90d09cb1156718/nodejs/misc.js
このあたりの実装を使うと簡単に試すことが出来るのでぜひ挑戦してみてください。

## misc

### SkyWay WebRTC Gateway良さそう

サービスとして提供するのであれば、不適切なユーザへの対応などが求められることだったり、エビデンスを残すなどの処理が必要になってくることもあるでしょう。

こういった要件に対して [SkyWay WebRTC Gateway](https://github.com/skyway/skyway-webrtc-gateway) が上手くハマるのではないかと睨んでいます。

https://github.com/skyway/skyway-webrtc-gateway/blob/master/api/api.yaml

例えばPeerの作成や削除をクライアントで行うのではなく、Peerの管理をAPI側で行うようにすれば特定のユーザをroomからBANするみたいな処理が可能になるかもしれません。

またエビデンスを残すみたいな用途に関しては

https://qiita.com/daisukeoda/items/86691bdb60d101b0e8d7

こちらを参考にして動画や音声を保存するみたいのが出来そうなので試してみたいですね。