サマリ 📖
年末年始にまとまった時間が取れたのでFlutterの勉強を兼ねて自作ワンタイムパスワードアプリ1(Google Authenticatorもどき)を作りました。
下記GIFのように、2段階認証用のQRコードを読み取って、ワンタイムパスワードを取得することができます。
現状Googleにしか対応していませんが、QRコードを読み取り正しいワンタイムパスワードを発行し、Googleログイン時の2段階認証を通過できます。
(GIF中で使っているQRコードはダミーです。qrencode
で適当に作りました)
概要編 👨🏫
ワンタイムパスワードの仕組み
基本的にGoogle Authenticator等のワンタイムパスワードを発行するサービスは、TOTP(Time-based One-time Password)アルゴリズムに従っています。
TOTPアルゴリズムはRFC6238で定義されたアルゴリズムで、サーバとクライアントが共有する秘密鍵および現在時刻から確認用のコードを生成するものです。
仕組みは非常にシンプルで分かりやすいです。
- QRコードには秘密鍵が含まれている
- クライアントはQRコードを読み取り秘密鍵を入手
- 秘密鍵 + 現在時刻をもとにパスコードを算出。サーバに送信
- サーバも同様の秘密鍵を持っているのでパスコードを算出し一致を確認する
つまり、TOTPはざっくり言うと現在時刻(unix時間)と秘密鍵で決まる関数2です。
そのため、秘密鍵(と各種設定値)さえサーバとクライアントで共有できれば全く同じコードを生成できます。
下記の図が非常に分かりやすかったです。
例えばGoogleサービスの場合だとQRコードを読み取ると下記のような文字列が取得できます3。
otpauth://totp/Google:{user_name}@{service_name}?secret={secret_base32}&issuer=Google
secret
部分がbase32
でエンコードされた秘密鍵を表しています。
こちらと現在時刻を組み合わせてパスコードを算出します。
正確なアルゴリズムの内容については前述のリンク先等が詳しいです。
実装編 👨💻
TOTPアルゴリズム
自身で実装しても良かったのですが、便利なパッケージ(dart-otp
)があったので利用しました。
興味ある方は自前実装するのもアリだと思います。ロジック自体はDartコードで100-200行くらいで実装できます。
QRコード読み取り
下記を参考に実装しました。
QRコード関連パッケージにはbarcode_scan
とqr_code_scanner
等があります。
前者は開発が止まっているようですが、monoさんがbarcode_scan2
として復活させている模様。
今回は実装した後にbarcode_scan2
の存在を知ったためqr_code_scanner
を使っています。
実装する上で特に手詰まりはありませんでしたが、強いて言うなら、QRコードが表示されている間、無限に読み取りのコールバックが呼ばれる問題がありました。
公式のIssueの通り、一度読み取った時点でfirstDetection
フラグを立て、後続のコールバックを止めるようにしました。
アニメーション
アニメーションはトップ画面のカウントダウンタイマー的な部分で使っています。
当初Flutterアニメーションの理解が非常に弱かったのですが、itomeさんの記事を読んでかなり理解が深まりました。
メチャクチャ分かりやすかったです。
また、Flutterを書き進めるとなるべくStatefulWidget
を廃したい力学が働いてきます(?)が、アニメーションについては素直にStatefulWidget
で実装しても良い気がしました(今回もStatefulWiget
で実装しています)。
サークルの描画自体の実装はこちらを参考にしました。
余談:Flutterのキャッチアップ方法
完全にFlutter初見だったので、まずは下記、社の先輩が書いた記事を参考にさせていただき、ざっくりとFlutterの全体感を掴みました。
contextやWidgetツリー等の各種ツリー、Riverpodの裏側の理解はこちらの記事(有料ですが)が非常に分かりやすかったです。
その他参考
GIFを作るのに参考にさせていただきました。