NCMBではID/パスワード認証やメールアドレス、ソーシャルメディアを使った認証を提供しています。しかし昨今ではセキュリティを高めるために多要素認証を用いているサイトが多数あります。
そこで、Twilioを使ってSMSによる確認コードを経由した会員登録ができないか試してみました。
TwilioによるSMS認証の流れ
まずTwilio APIの流れを紹介します。実装時はREST APIを直接叩くことになるので、ライブラリではなくcurlコマンドベースでの紹介になります。
まずTwilio上でユーザを作成します。必要な要素はメールアドレス、電話番号、そして国コードです。
curl "https://api.authy.com/protected/json/users/new" \ -H "X-Authy-API-Key: AAAAAAAAAAA" \ -d user[email]=atsushi@moongift.jp \ -d user[cellphone]=909999999 \ -d user[country_code]=81
レスポンスはユーザ作成しましたというメッセージになります。この時、ユーザID(今回は 3355759)も作成されます。
{ "message": "ユーザーの作成に成功しました。", "user": { "id": 3355759 }, "success": true }
次にこのユーザ宛にSMSを送ります。
curl https://api.authy.com/protected/json/sms/3355759?force=true \ -H "X-Authy-API-Key: AAAAAAAAAAA"
レスポンスはSMSを送ったというメッセージになります。
{ "success":true, "message":"SMS token was sent", "cellphone":"+81-XXX-XXX-XX99" }
そして携帯電話宛にSMSが届きます。例えばコードが8660969だったとします。そのコードとユーザIDを使ってAPIを呼びます。
curl https://api.authy.com/protected/json/verify/8660969/3355759 \ -H "X-Authy-API-Key: AAAAAAAAAAA"
コードが合っていれば、ユーザ登録完了した旨のメッセージが届きます。
{"message":"Token is valid.","token":"is valid","success":"true","device":{"id":null,"os_type":"sms","registration_date":1527067667,"registration_method":null,"registration_country":null,"registration_region":null,"registration_city":null,"country":null,"region":null,"city":null,"ip":null,"last_account_recovery_at":null,"last_sync_date":null}}
仕組みを考える
以上の流れをNCMBの認証と結びつけていきます。
- メールアドレス、パスワード、電話番号を入力してNCMBへ送信
- スクリプト機能を使ってTwilioのユーザ作成/SMS送信
- 認証用コードをNCMBに送信
- Twilioで確認。問題なければユーザ登録を完了させる
注意点
会員登録を実行してしまうと、ログインできる状態になってしまいます(当たり前ですが)。そこで、SMS認証前のフラグを設けておいて、そのフラグが立っていたらログイン実行後にエラーとする判定を加えます。
TwilioはCORSによる制限でWebブラウザからの操作は許可していません。そこでスクリプトを使います。
登録処理
登録時には、下記の情報をスクリプトに送ります。
- メールアドレス
- パスワード
- 携帯電話番号
具体的には次のようになります。電話番号は念のため -
と頭の0を削除します。
$('#register').on('click', e => { e.preventDefault(); const params = { email: $('#inputEmail').val(), password: $('#inputPassword').val(), tel: $('#inputTel').val().replace(/\-/g, '').replace(/^0/, '') }; ncmb.Script .data(params) .exec('POST', 'register.js') .then(res => { const body = typeof res.body == 'string' ? JSON.parse(res.body) : res.body; userId = body.id; $('.register').show().text(body.message); }) .catch(err => { console.log(err); }); });
スクリプトの処理
スクリプトは以下の処理を実行します。
- Twilioのユーザ登録
- TwilioでSMS送信
- NCMBのユーザ登録
処理が成功した場合にはTwilioのユーザIDを返します。これは後のSMS認証時に利用するものです。
const request = require('superagent'); const NCMB = require('ncmb'); const applicationKey = 'APPLICATION_KEY'; const clientKey = 'CLIENT_KEY'; const ncmb = new NCMB(applicationKey, clientKey); module.exports = (req, res) => { const twilioToken = 'TWILIO_TOKEN'; const params = req.body; let userId = null; // Twilioのユーザ登録 request .post('https://api.authy.com/protected/json/users/new') .set({ 'X-Authy-API-Key': twilioToken }) .send({ user: { email: params.email, cellphone: params.tel, country_code: 81 } }) .then(response => { // TwilioのSMS送信 const body = response.body; userId = body.user.id; return request .get(`https://api.authy.com/protected/json/sms/${body.user.id}?force=true`) .set({ 'X-Authy-API-Key': twilioToken }) .send(); }) .then(response => { // NCMBのユーザ登録 const acl = new ncmb.Acl; acl .setPublicReadAccess(true) .setPublicWriteAccess(true); const user = new ncmb.User(); return user .set('userName', params.email) .set('mailAddress', params.email) .set('cellphone', params.tel) .set('password', params.password) .set('twilioUserId', userId) .set('confirm', false) .set('acl', acl) .save(); }) .then(response => { res.json({ id: userId, message: '登録完了しました' }); }) .catch(err => { res.json(JSON.parse(err.response.text)).status(err.status) }) }
ユーザを作成する際に、ACLを全員に対して読み書きを許可しています。これは後で更新するためですが、より安全にするためには管理ユーザを作成し、そのユーザだけ書き込みを許可するようにしたほうが良いでしょう。今回は簡易的にしています。
ここまでの処理が終わると会員管理に新しいユーザが追加され、携帯電話にSMSが飛んできます。
SMS認証処理
認証処理では以下の情報をスクリプトに送信します。
- TwilioのユーザID
- SMSの認証コード
Webブラウザ側の処理は次のようになります。
$('#confirm').on('click', e => { e.preventDefault(); const params = { confirm: $('#inputCode').val(), userId: userId }; ncmb.Script .data(params) .exec('POST', 'confirm.js') .then(res => { const json = JSON.parse(res.body) $('.confirm').show().text(json.message); }) .catch(err => { console.log(err); }); })
スクリプトの処理
スクリプトでは以下の処理を行います。
- TwilioのSMS認証
- NCMBのユーザを取得
- NCMBのユーザ情報を更新
NCMBのユーザ情報はTwilioのユーザIDを使って検索します。また、会員情報を更新する際にはACLも忘れずに更新しておきます。
const request = require('superagent'); const NCMB = require('ncmb'); const applicationKey = 'APPLICATION_KEY'; const clientKey = 'CLIENT_KEY'; const ncmb = new NCMB(applicationKey, clientKey); module.exports = (req, res) => { const twilioToken = 'TWILIO_TOKEN'; const params = req.body; // TwilioのSMS認証 request .get(`https://api.authy.com/protected/json/verify/${params.confirm}/${params.userId}`) .set({ 'X-Authy-API-Key': twilioToken }) .send() .then(response => { if (response.body.success == 'true') { // NCMBのユーザを取得 return ncmb.User .equalTo('twilioUserId', params.userId) .fetch() } else { res.send(response.body).status(401); } }) .then(user => { // NCMBのユーザ情報を更新 const acl = new ncmb.Acl; acl .setPublicReadAccess(true) .setUserReadAccess(user, true) .setUserWriteAccess(user, true); return user .set('confirm', false) .set('acl', acl) .update(); }) .then(() => { return res.json({message: "完了"}) }) .catch(err => { console.log(err) res.json(JSON.parse(err.response.text)).status(err.status) }) }
まとめ
後はログイン時にconfirmフラグを見て、ログイン可否を決定すれば良いだけです。
ここまでの処理でTwilioのSMS送信を使ったNCMBの会員登録処理が完了しました。なお、メールアドレスを登録しているので、メールアドレスやパスワード変更する際にはメールアドレスの確認が取れていなければなりません。メールアドレスは普段と異なる場所に保存しておけば良いですが、パスワード変更ができないのが問題になりそうです。実際のフローではダミーのパスワードを一旦与えておき、会員登録完了としてパスワード設定を行ってもらう(パスワード忘れのメールを使って)といった工夫が必要そうです。
今回のコードはNCMBMania/SMSAuthにアップロードしてあります。実装時の参考にしてください。