昨今のセキュリティ問題もあり、注目されているのが多要素認証です。IDとパスワード以外の情報を使って認証を行うもので、ワンタイムコードを生成したり、SMSで別途コードを送ったりします。
執筆時点(2019年08月)のmBaaSは標準では多要素認証をサポートしていません。そこで、若干無理矢理ながら多要素認証を頑張って実装してみたいと思います。
要件
今回の要件は次のようになります。
- 多要素認証はSMSで行う
- SMS送信はTwilioを利用
- ID/パスワード認証を利用
- 重要な処理(決済など)は多要素認証が通過しているデバイスからしか行えない
クラスについて
データストアのクラス(テーブル相当)は次のような役割分担になります。
- User
会員情報が入ったテーブル。標準のものを利用。 - Token
SMSで送信するトークンを格納 - Device
デバイスごとにSMS認証を通過しているかを格納
ここで大事なのは、Tokenクラスはスクリプトからしか触らないテーブルになります。また、Deviceクラスは作成と閲覧はできますが、更新は管理者権限が必要なクラスになります。誰でも更新できるようにしてしまうと、任意のデバイス情報についてSMS認証済みにできてしまうでしょう。それを防ぐために必須です。
セキュアな処理を行う場合
ログイン時にDeviceテーブルをチェックします。そして、各デバイスが多要素認証済みであるかどうか判定します。ログインはできるけれど、重要な処理は実行できないという状態にします。重要な処理を実行するには、各デバイスで多要素認証を通過しなければなりません。
実装について
会員登録処理
会員登録処理では、以下の処理を行います。
- 通常のID/パスワードによる会員登録
- スクリプトを使ったSMS送信
1は通常のデータストア操作ですが、2はスクリプトを経由して行います。処理としては次のようになります。
- コード(数字4桁)を生成
- TwilioでSMS送信
SMS送信するためには携帯電話番号が分からなければなりません。そのため、会員情報は管理者グループから閲覧可能であることとします。
const twilio = require('twilio'); module.exports = (req, res) => { const accountSid = 'ACf...7df'; const authToken = '01b...619'; const fromTel = '+17653003967'; const client = new twilio(accountSid, authToken); const message = client.messages.create({ body: req.query.message, to: req.query.to, from: fromTel }).then(message => { res.json(message); }) }
コード入力処理
SMSで受け取ったコードをmBaaSに送信する処理です。この時必要なパラメータは以下になります。
名前 | 意味 |
---|---|
code | 認証コード |
user_id | 自分自身のobjectId |
device_id | デバイス単位で生成する固有のID |
なお、認証コードの総当たりを防ぐため、ユーザのobjectIdごとに失敗回数を記録しておき、閾値を超えた場合にはアカウントをロックするとした方が良いでしょう。この処理もスクリプトで行う必要があります。
const response = await ncmb.Script .data({ user_id: user.objectId, code: code, device_id: device_id }) .exec('POST', 'totp.js');
ログイン処理
ログイン処理を行った際に、Deviceクラスも検索します。ここに情報がある場合は認証済みです。
const Device = ncmb.DataStore('Device'); const device = await Device .equalTo('user_id', user.objectId) .equalTo('device_id', device_id) .fetch(); // 認証済みデバイスの場合 true const auth = device.objectId !== '';
セキュアな処理を実行
決済やクレジットカード情報の紐付けなど、セキュアな処理を行う際にはDeviceクラスの情報を確認して、多要素認証を行っているかどうかチェックします。セキュアな処理はすべてスクリプトで行う必要があります。その際には会員の権限ではなく、管理者権限で実行するのが良いでしょう。万一クラス名が分かったとしても、データの更新や削除はできません(クラスのパーミッションで指定するのがベストです)。
if (auth) { // セキュアな処理を実行 // authフラグはクライアントで変更できてしまうので、実際の検証はスクリプトで行います } else { // 未認証デバイス }
まとめ
本来の多要素認証では、多要素認証を通過していない場合にはログインもできないのが基本です。しかしmBaaSの場合はID/パスワード認証を提供しているので、多要素認証を通過せずとも認証できてしまいます。それを制限しようと思うと、通常の会員クラスとは別なクラスにパスワードを保存しておく必要があるでしょう。それはセキュリティ上のリスクになります。
Amazonなどで買い物する前にパスワード入力が強制されるようなイメージになります。SMS認証を一段階遅らせられるので、ユーザとしてはすぐにアプリ操作に入れるのは利点かも知れません。