Twilio+Deviseで電話番号(SMS)ユーザログイン機能を実装

電話番号認証、サービスによってはすごい比率が高いらしいので実装したいなと思います。

目次

方針の検討

思いつくいい感じの方法は、3つほど。

  1. 電話からの音声案内
  2. 情報をアプリケーション側で開示した後、ユーザがSMSを送信
  3. アプリケーション側から6桁の番号を送信、ユーザはそれをブラウザから入力

3番が一番よくあるパターンだと思いますが、まずは1と2から検討してきたいと思います。

1. 電話からの音声案内

いい感じとは書いたものの、1番は手間的な意味でかなりハードルが高いので却下。SMSって届かないことがあるとよくきくので(rebuildでDaiseさんが出てるときもそんな話があったような)、大きくなったらリスクヘッジ的な意味で導入できたらいいなって感じですね。

2. 情報をアプリケーション側で開示した後、ユーザがSMSを送信

以下の記事で、受信のみで電話番号認証を行っている方法をみかけました。なるほど...(こういうのを知見というのだろう)

qiita.com

これだと「安い」ってメリットはあると思うのですが、

  • コモンなインターフェースでないのでユーザが戸惑うかも
  • 「ユーザからSMSを送信してもらう」ことはハードルが少し上がる

というデメリットがあるなと思いました。

3. アプリケーション側から6桁の番号を送信、ユーザはそれをブラウザから入力

ということでお金を払えるなら、3番の方法がやはり一番いいと思いました。

実装手順


1. Twillio準備

Twilioに登録して、USの電話番号でもなんでもいいので、SMSを使える番号を取得します。

USの番号だとSMSを使えるみたいなので、取得。

TwillioのREST APIは以下から参照できます

Twilio Docs: REST API Reference

APIのバージョニング日付でやってるのか。

SMS送信は以下。

Twilio Docs: Send text messages with Twilio's REST API

$ curl -X POST 'https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/SMS/Messages.json' \
-d 'From={+81のような国際電話形式の送信元電話番号}' \
-d 'To={+81のような国際電話形式の送信先電話番号}' \
-d 'Body={メッセージ本文}' \
-u {AccountSid}:{AuthToken}

AccountSid, AuthTokeは以下のURLで確認できます。

https://jp.twilio.com/user/account/messaging/dashboard

Rubyでの使い方、twillio-rubyの使い方は以下を参照。

Twilio を使って Rails から ショートメール(SMS) を送信する - Shred IT!!!!

こんな感じ。

gist86bd470a2b5beff9f0fb

届いた!なんか感動嬉しい。

2. 実装

以下を参考にさせていただきながら。

ログイン機能にdeviseを導入して しまった いるので、若干つらいことになる。自分は以下のような仕様で実装しました。

  • 電話番号での登録時にはfakeのemailを登録する
  • 登録の流れは以下
    1. formのデータをPOST, 認証コード入力画面に遷移
    2. 認証コードを生成・ユーザのデータと一緒に保存
    3. SMSで認証コードを送信
    4. 認証コードが入力、POSTされたら正しいことを確認してuserのデータをverifiedにする
  • 「電話番号またはemailでログイン」できるようにする
  • 認証コードをfillしてPOSTしたら自動でログインする
  • UserにSMSログイン用のカラムを追加
    • sms_confirmed: boolean ... SMSの認証がされたかどうか
    • sms_token: string ... 認証コード自体の保存場所

基本的にUsers::RegistrationsControllerUsers::SessionsControllerにアクションを追加して実装した。

3. テスト

各モデルのメソッド単体テストを書けるとしても、feature specをどうするか悩みました。

ここの記事を参考にして書きました。

robots.thoughtbot.com

日本語訳: SMSインタラクションのテスト | プログラミング | POSTD

具体的には、以下のようなFakeSmsというクラスをつくり、これをTwilioのクラスに定数としてスタビングする。

# 例えば spec/support/fake_sms.rb
class FakeSms
  Message = Struct.new(:from, :to, :body)

  cattr_accessor :messages
  self.messages = []

  def initialize(_account_sid, _auth_token)
  end

  def account
    self
  end

  def messages
    self
  end

  def create(from:, to:, body:)
    self.class.messages << Message.new(from: from, to: to, body: body)
  end
end

(こうやってハッシュ受け取れるんや)

そしてスタブ。

# spec/spec_helper.rb

RSpec.configure. do |config|
  config.before(:each) do
    stub_const("Twilio::REST::Client", FakeSms)
  end
end

大したことじゃないですがクラス名FakeSMSFakeSmsか悩む。キャメルケースがFakeSMSならスネークケースだとfake_s_m_sじゃんとか思うとつらい。SMSとして1単語とした方がいいなーとか。

おわり

ということで、自前で電話番号ログイン機能を実装してみました。

機能的には複雑ではないですがテストなど若干つまずいたとこもありました。

実際のコードは省略しちゃったので、何かご質問等あれば気軽に @totzyuta にでも投げてやってくださいー