某プログラミングスクールのチーム課題制作において、最近はやり(?)のSMSによる2段階認証を実装してみたくなりました。その際の振り返りとメモです。
色々間違っている所とか、改善すべき所とかあると思います。
指摘をいただけると非常に喜びます!!よろしくお願いいたします。
はじめに
今回利用したのは、Twilioと言うサービスです。このサービスは、電話発着信、SMS送信などの機能をWebサービスAPIとして提供してくれていて、非常に簡単にSMS認証を実現できます。
ただ、初期費用として2,000円分のTwilioポイントチャージが必要です。
私は好奇心?というか、自己満足欲を満たすために手を出してみました。
なお、2019年8月から1ヶ月間、チーム開発でガッツリ使いましたが400円ほど残高残っている状況でした。
<費用の内訳>
電話番号取得:月額149.99995円(米国産)〜
SMS送信:8円/1通
※自動チャージオフにしていると費用使い切り次第アカウントが無効になるようです。
就活以降も使いたい等場合は、ちょっと注意かも。
大まかな流れ
今回の説明の流れを最初に示します。
【ザックリした流れ】
[電話番号を取得しよう]
↓
[Railsプロジェクトへの設定追加]
↓
[API動作確認〜ショートメッセージ[Hello World!]を送ってみる〜]
↓
[承認コード生成用のメソッド作成]
↓
[RailsプロジェクトにAPIを組み込む]
↓
[!!!!完成!!!!]
##電話番号を取得しよう
まずは公式サイトにアクセスし、サインアップしてください。サインアップ後にやることは、大きく2つ。
① プロジェクトの登録
トップ画面左上からプロジェクトの作成が可能です。(Create New Project)
目的は適当に[電話番号認証]とかにしてチャチャっと作成した・・・はず(うろ覚え
プロジェクトが作成できれば、API利用に必要となるAPIキーがダッシュボードから参照可能になります。
後から使う値:アカウントSID/AUTHTOKEN
② 電話番号の購入
トップ画面左にある○に・・・がついたボタンを押すとサイドメニューが開きます。
そこから[# Phone Numbers]と言うメニューを選択しましょう。
さらに出てくるサイドメニューから[電話番号の購入]を選択し、検索画面を開きましょう。
今回はSMS送信がしたいので、機能のSMSに☑️を入れて検索し、適当な電話番号を購入しましょう。
※電話番号未購入なら、プロジェクトのダッシュボードにもメニューが出現するかも・・・?
安さ重視なら米国産が良いかと思います。
私は、米国産の149.99995円/月額を購入しました。
後から使う値:購入した電話番号
##Railsプロジェクトへの設定追加
事前準備としてgemおよび、config/initilizerへの設定追加を行います。
###gemの追加
今回は以下2つのgemを使いました。
・twilio-ruby
携帯にショートメールを送るために必要
・phony
twilioでのSMS送信は電話番号が国際規格(+81とかで始まるやつ)である必要があります。
このgemを使うと簡単に正規化が可能です。
以下のようにGemfileにgemを追加してbundle install
してください。
gem 'twilio-ruby', '~> 5.26.0'
gem 'phony_rails'
最新のバージョンはgithubのreadmeに記載があると思うので確認してみてください。
【GitHub】twillio-ruby/phony_rails
initilizerへの設定追加
利用する度にやっても良い気がしますが、今回はサーバ起動時に読み込まれるinitilizerに設定を組み込みました。
require 'twilio-ruby'
Twilio.configure do |config|
config.account_sid = Rails.application.credentials.twilio[:TWILIO_ACCOUNT_SID]
config.auth_token = Rails.application.credentials.twilio[:TWILIO_AUTH_TOKEN]
end
なお、APIキーなどの秘匿情報はcredential.yml側に書き込みます。
credential.ymlの編集方法はコチラを参考にしてください。
initializers/twilio.rbのとおりに呼び出したい場合は、以下のように指定すればOKです。
twilio:
TWILIO_ACCOUNT_SID: -twilioで取得したsid-
TWILIO_AUTH_TOKEN: -twilioで取得した認証トークン-
TWILIO_PHONE_NUMBER: -twilioで取得した発信用電話番号(国際規格で入力)-
これで下準備は完了!
##API動作確認〜ショートメッセージ[Hello World!]を送ってみる〜
組み込む前にAPIの動作確認をしておきましょう。
ターミナルからrails c
でコンソールを立ち上げて、以下の様に入力・実行してください。
※もちろんtwilioの設定を入れたプロジェクトフォルダに移動してからですよ!
$ rails c
[1] pry(main)> client = Twilio::REST::Client.new
=> #<Twilio::REST::Client:0x00007fb5c4e7d048・・・>
[2] pry(main)> client.messages.create(
[2] pry(main)* from: Rails.application.credentials.twilio[:TWILIO_PHONE_NUMBER], #送信元電話番号
[2] pry(main)* to: "+8190xxxxxxxx", #送信先電話番号(今回は自分のスマホ電話番号)
[2] pry(main)* body: "Hello World!") #メッセージbody
=> <Twilio.Api.V2010.MessageInstance account_sid:**中略**.json>
設定や入力に不備がなければ、自分のスマホにSMSが届いたはずです!!!
この辺りから、かなりテンションが上がってきます。
##承認コード生成用のメソッド作成
SMS送信の下地は出来ましたが、肝心の認証コードが無いと認証になりません。
HTTPのセッションキーなどの生成に使われているrubyのsecurerandom
メソッドを使って、認証用のコードを生成させます。
module RandomValueGenerator
extend ActiveSupport::Concern
require "securerandom"
def random_number_generator(n)
''.tap { |s| n.times { s << rand(0..9).to_s } }
end
end
使おうとしているcontrollerのprivateメソッドとして組み込むのが一番早いと思います。
が、Concernを使ってみたかったので,無理やりくくり出してみました。
これで、どのControllerからでもrandom_number_generator(4)
と指定したら4桁のランダム整数を取得することが可能になりました。
"securerandom"が気になった方はコチラで確認してみてください。
RailsプロジェクトにAPIを組み込む
今までの知識や下準備した結果を元に実際にControllerからSMSを送信して、認証コードのチェックをしてみましょう。
※メソッドどこに書くか、メソッド名、renerやredirect先のアクションやパスは、作成者側に委ねます。
class Xxxx::XxxsController
include RandomValueGenerator
def send_secure_code #SMSの送信
# 必要な変数の下準備をします。
# 1:電話番号を国際規格に整形。ここでgem'phony'を使ってます
# 2:認証コードを生成して、session変数に格納※認証コードのチェックを次のページでやりたいので
send_phone_number = PhonyRails.normalize_number '電話番号(090~)を格納した変数', country_code:'JP' # 電話番号を+81にフォーマット
session[:secure_code] = random_number_generator(4)
# twilioのMessagingAPIを使用してSMSに認証コード送信
begin
client = Twilio::REST::Client.new
result = client.messages.create(
from: Rails.application.credentials.twilio[:TWILIO_PHONE_NUMBER],
to: send_phone_number,
body: "認証番号:#{session[:secure_code]} この番号を●●の画面で入力してください。"
)
rescue Twilio::REST::RestError => e
@messages = "エラーコード[#{e.code}] :” #{e.message}”"
#こんな感じで出ます。日本語化するならymlに追加する感じでしょうか・・・?_?
#エラーコード[21604] :” Unable to create record: A 'To' phone number is required.”
render :previous_action
end
end
def check_secure_code #認証コードのチェック
# 入力チェック
if params[:secure_code].present?
else
@messages = '認証番号を入力してください'
render :previous_action
end
# 認証コードのチェック実施。また、結果を受けてレンダリング先を決める
if session[:secure_code] == params[:secure_code]
redirect_to :next_action
else
@messages = '認証番号が一致しません'
render :previous_action
end
end
end
入力チェックをせず、'認証番号が一致しません'って言うだけでも良いかもですね。
あと、Controllerメソッド書きましたが、これらもConcernなどにくくり出した方が良い気がしますね。。。
初心者感満載ですみません。。
!!!!完了!!!!
これで、やりたかったことを一先ずクリアしました。文中でも書いたとおり、controllerで記載すべきでない処理があったり、sessionに突っ込んだsecure_codeなどは本当にそれで良いのか?など、気になるところは有りますが、今後の改善課題としたいなと思います。
#まとめ
実装開始した時は分からな過ぎて路頭に迷いましたが、振り返ってみれば簡単に実装出来ちゃうものなんだなって思います。実装できて初めて他の記事が言ってることもちょっと理解・・・orz
ちなみに、messagingAPI以外にvoiceAPIと言うのを利用すれば電話による認証コード確認も比較的簡易に実装できそうでした。
・・・が、別なことに時間を割くことにしました。時間ができれば試してみたい!
以上です。
参考
TwilioとRailsでSMS認証機能を実装した話
TwilioとRailsで、1通1円のSMS認証を実装してみる
Ruby / Rails における E164な国際電話番号の扱い方
[Ruby 2.x] SecureRandom を利用した乱数の生成方法