ブラウザでMFA認証

# この記事について

MFA認証はご存知でしょうか。Multi-Factor Authenticationのことで日本語でいうと「多要素認証」となります。IDとパスワードだけなんてセキュアじゃない！記憶に基づいた認証と、本人しか持ち得ない何かを持っていることなど、多数の要素に基づいて認証しようじゃないか！ということだと思いますたぶん。

正確な用語定義はおいといて、MFA認証といえばAWSのマネジメントコンソールにログインする際に有効にしている方も多いのではないでしょうか。私も有効にしています。

最初に書いたように、AWSのMFAは「IDとパスワード」と「認証コードを生成するデバイスの所有」を認証要素として採用しています。つまり別途物理的なデバイスが必要になります。多くの方はスマートフォンにGoogle Authenticatorなどの認証コード生成アプリケーションをインストールしてスマフォを「仮想デバイス」として扱っていると思います。

それでもいいのですが、自社オフィス内でログインの度にスマフォを出したりしまったりして、認証アプリをいちいち起動するのもめんどくさいです。そもそも専用デバイスもスマフォも支給されてないので、MFA認証使えない…って方もいると思います。

「PCのブラウザで表示できればいいのになあ」と思ったことはないでしょうか？私も思いました。そして作りました。JavaScriptで。この記事はそのJavaScriptライブラリとそれを使ったWebページアプリケーションの紹介となります。

# AWSのMFA認証コード

[こちらのドキュメント](https://aws.amazon.com/jp/iam/details/mfa/)にも書いてあるように、[TOTP](https://tools.ietf.org/html/rfc6238)です。RFC 6238で仕様化されているので誰でも実装できます。

TOTPは一定時間ごとに変化する[HOTP(RFC 4226)](https://tools.ietf.org/html/rfc4226)です。HOTPはHMAC-based One-Time Password algorithmのことで、HMAC-SHA-1を使ってます。つまり、[HMAC(RFC 2104)](https://tools.ietf.org/html/rfc2104)と[SHA1(RFC 3174)](https://tools.ietf.org/html/rfc3174)です。

上記の仕様を全て実装すれば認証コードが生成できます。
幸いなことにJavaScriptではcrypto-jsというライブラリがあったり、Web Crypto APIが標準化されていたりする(のかな？大体のブラウザで実装されてる)のでそれらを使えばHMAC-SHA-1は自前で実装せずとも大丈夫です、が紹介するライブラリはそれらを一切使っていません。理由は、何かに依存するというのはセキュリティ的にちょっとどうなんだろうということと、後者は非同期APIなので使いたくありませんでした[^1]。なんで最近の仕様ではなんでもかんでもPromise返すんでしょうね。シングルスレッドで動作するんだからブロックなんてしてられない、ってのはわかるんですがそれにしてもハッシュするくらいねえ…。使い辛くてしょうがない。


# JavaScriptライブラリとアプリ

愚痴を漏らしてしまいましたが、ライブラリとそれを使ったアプリの紹介です。

* [blirhotp](https://github.com/tempusfugit-lab/blirhotp) HOTP実装。
* [browsertotp](https://github.com/tempusfugit-lab/browsertotp) blirhotpを使いTOTPを実装したHTML+JavaScript。Google Authenticatorみたいなアプリ。
* [ngtotp](https://github.com/tempusfugit-lab/ngtotp) browsertotpのAngularJSによる実装版。

ライブラリではHOTPまでを実装してます。アプリケーション側で30秒ごとに値を更新してTOTPを実現しています。勉強目的でAngularJS版も作ったのですが、どうも初回のアニメーションがうまく表示されません。AngularJS難しいですね…。

ではアプリケーションの使い方です。browsertotpの画面で説明します。
browsertotpの[GitHub Pages](http://tempusfugit-lab.github.io/browsertotp/)をブラウザで開いてみてください。

![bh1.png](https://qiita-image-store.s3.amazonaws.com/0/137783/abe4f9e9-1795-f7d4-f59d-0fdb36e0a526.png)

まだ何も登録していないので30秒で空になるメーターが動いてるだけです。
せっかくなのでここにAWSのIAMユーザーを登録してみましょう。

AWSマネジメントコンソールからIAMユーザーの認証情報を開いて、「MFAデバイスの管理」ボタンを押します。
![aws1.png](https://qiita-image-store.s3.amazonaws.com/0/137783/9bee9a73-19a9-71fe-2f57-37ba0e1a7d11.png)

ダイアログが表示されるので「仮想MFAデバイス」を選択して次へ。
![aws2.png](https://qiita-image-store.s3.amazonaws.com/0/137783/cb186031-1c43-812c-2b1b-96ce3ced11b3.png)

確認メッセージが表示される場合がありますが、次へ。
![aws3.png](https://qiita-image-store.s3.amazonaws.com/0/137783/8f02eaaa-11ab-d69e-6e17-d9b1171cfd3a.png)

QRコードのあるダイアログが表示されるので、「シークレット設定キーを表示」を選択して設定キーをコピーします。
![aws4.png](https://qiita-image-store.s3.amazonaws.com/0/137783/9d3ab64b-5964-7bc6-d258-db1acf0225d4.png)

browsertotpに戻り、右上または右下の＋マークを押して、登録ダイアログを表示します。
そしてNameにわかりやすい好きな名前(IAMユーザー名など)、Keyに先ほどコピーした設定キーをペーストし、okボタンを押します。
![bh2.png](https://qiita-image-store.s3.amazonaws.com/0/137783/0b2d3a20-4358-94cb-e817-b36b61514392.png)

登録が完了し、認証コードが表示されます。
![bh3.png](https://qiita-image-store.s3.amazonaws.com/0/137783/ec8c149b-311a-f0c5-2a01-55c30b94b2d2.png)

AWSの方に戻ってbrowsertotpに表示される認証コードを**２連続分**認証コード１と認証コード２に入力し、「仮想MFAの有効化」ボタンを押します。
![aws5.png](https://qiita-image-store.s3.amazonaws.com/0/137783/fe33451e-24d5-afd1-df7e-24d0bb2e17ba.png)

認証コードが正しければ完了です。
![aws6.png](https://qiita-image-store.s3.amazonaws.com/0/137783/7c639a60-d1db-ac8f-417e-428e8b608656.png)

以降はbrowsertotpが生成したコードをMFA認証に使用してください。
![aws7.png](https://qiita-image-store.s3.amazonaws.com/0/137783/4eae698e-bb81-f7ec-0fd5-b52208309f6d.png)


# 鍵を登録して大丈夫なの？
ブラウザなんかに入力して、鍵がサーバに送信されないのか？と思われるでしょうが、**送信されません**。大丈夫です。HTTP通信上にも鍵データは出現しませんので盗聴もされません。紹介したアプリケーションは、HTMLとJavaScriptの静的コンテンツです。処理はクライアント側(ブラウザ内)のみで完結します。鍵(設定キー)データはどこに保存されているかというと、ブラウザのLocal Storageに保存されています。よって同一オリジンからしかデータは参照できません。

もちろんGitHub Pagesのものをそのまま利用すると、私の組織アカウントのオリジンからは鍵データにアクセスできてしまいますが、妙な処理を入れてデータを収集しないことを誓います。

ただし、当アプリケーションはMITライセンスに基づき、何らの保証もなく提供されます。GitHubページのものはデモとして試し、実際に使う場合は`git clone`してLAN内のWebサーバで公開するのがいいでしょう。静的コンテンツなので簡単です。
ライブラリ、アプリケーションともMITライセンスですので、ご自由に修正などしていただいて構いません。

一方、`git clone`してfileスキーム(file://)で動作させるのは**お勧めしません**。他のfileスキームで動作するページからデータが参照できてしまいます。いや、変なものを動かしてないから大丈夫だ！と思っても、ちょっとしたJavaScriptの処理をテストする際にLocal Storageのデータを全部消しちゃったとかもありますからね。



では、素敵なMFA体験を！

***
[^1]: なんだかんだいってただ自分で実装したかっただけです。アプリの方はAngularJSや[Sortable](https://github.com/RubaXa/Sortable)をありがたく使わせてもらっています。非同期APIが使い辛いというのは本当です。