[Rails]Twilioを使った簡易SMS認証を作った

どうもこんばんは、プログラミング学習３ヶ月目くらいになったChihaと申します。
某スクールで某フリマアプリのコピーサイトのようなものをチームで開発しています。

タイトル通り、[Twilio](https://www.twilio.com/)というサービスを使った簡易SMS認証機能を実装したのですが、意外と記事が少なかったので書こうと思いました。
~~簡易的とはいえ折角実装したのに、諸事情により全行コメントアウトすることになったのが悔しいから記事を書いているとかそんな事ではないです。~~

# 開発環境
- Ruby on Rails 5.2.2
- Ruby 2.5.1

# 概要
ユーザーの入力した電話番号に向けてSMSで値を送り、ユーザーの端末に届いた値を入力させ、送った値と入力値を照合し、一致したらパスするという感じのやつです。
ユーザー認証の一環として不正利用の防止などの目的で導入されるようです。

今回はtwilioと呼ばれるクラウドAPIサービスを利用して実装しました。
>Twilioは音声通話、メッセージング（SMS/チャット）、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウドAPIサービスです。(https://twilio.kddi-web.com/)

だそうです。元はアメリカ産のサービスで、日本ではKDDIが代理店のようです。

今回はざっくり
電話番号受付→ランダム5桁の数字を生成→SMS送信→入力値と照合
のような流れで実装しました。

以下実際のコードになります。

# 詳細・コード
導入まではこちら（[TwilioとRailsでSMS認証機能を実装した話](https://blog.spacemarket.com/code/twilio-rails-sms/)）を参考にさせていただきました。

上記の記事の実装以降の内容を自分なりにかなり簡略化したものが当記事になります。

送信元となる電話番号とAPIの取得については、当記事では割愛させていただき、取得以降の流れを書いていきます。

### ①Gemのインストール
とりあえずGemのインストール

```ruby:Gemfile
 #twilio
 gem 'twilio-ruby'
 #環境変数用
 gem 'dotenv'
```
書いたら

```:ターミナル
$ bundle install
```
してあげます。

### ②APIの設定
APIならびに取得した電話番号はベタ書きせずにgit等の管理から外したファイルに定義します。
僕の場合はdotenvを使っています。

アプリ直下に.envというファイルを作り、先ほど取得したAPIを書き込みます。

```:.env
TWILLIO_SID="取得したSID"
TWILLIO_TOKEN="取得したトークン"
TWILLIO_NUMBER="取得した番号（+付き）"
```

こちらも絶対に忘れないように

```:.gitignore
/.env
```

これで環境変数のセットは完了したはずです。不安だったらrails consoleでENV["TWILLIO_SID"]とか入力してあげると、上手くセットできていたら.envに書き込んだ値が表示されます。

### ③ルーティング
今回は、5ページに渡るwizard形式の新規登録フォームの２ページ目で認証を実装しています。
そのため、少しルーティングが無理やりです。

```ruby:routes.rb
  #中略
  resources :signup ,only: [:index,:create] do
    collection do
      #中略
      #電話番号入力フォーム
      get 'sms_authentication' 
      #入力された番号へのSMS送信、送信した文字列の保持
      post 'sms_authentication' => 'signup#sms_post'
      #受信した値の入力フォーム
      get 'sms_confirmation' 
      #値の照合
      post 'sms_confirmation' => 'signup#sms_check'
      #一致したら下のパスに飛んで登録画面が続く
      get 'address' 
      #以下略
    end
  end
```
それぞれのルーティングの役割はこんな感じです。
正常に認証が進むと上から順に動いていきます。

最初の入力フォーム
<img width="390" alt="スクリーンショット 2019-09-21 0.19.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/ca077540-1456-bcdd-22a7-3ba906f0fb46.png">


```haml:sms_authentication.html.haml
    //中略
    = form_for @profile, url: sms_authentication_signup_index_path,method: :post do |f|
      = label :nickname, '携帯電話の番号'
      = f.text_field :tel, placeholder: '携帯電話の番号を入力'
      = f.submit 'SMSを送信する'
```
※見やすさ重視のためクラスや間に入っている要素は削って簡略化しています

ここで入力された値がルーティング1つ目のpostアクション(sms_post)へと飛びます。

```ruby:signup_controller.rb
  def sms_post
    @profile = Profile.new
    #パラメータが飛んでなかった場合ここでrender
    render sms_authentication_signup_index_path unless  profile_params[:tel].present?
    #電話番号を+81~の国際書式に書き換え（そうしないと送れない）
    phone_number = profile_params[:tel].sub(/\A./,'+81')
    #ランダムに5桁の整数を生成
    sms_number = rand(10000..99999)
    #後の認証用にsessionに預ける
    session[:sms_number] = sms_number
    #環境変数を使ってsms送信準備 
    client = Twilio::REST::Client.new(ENV["TWILLIO_SID"],ENV["TWILLIO_TOKEN"])
    #送信失敗した場合必ずエラーが出るので、例外処理で挙動を分岐
    begin 
      #生成した整数を文章にしたsms送信
      client.api.account.messages.create(from: ENV["TWILLIO_NUMBER"], to: phone_number, body: sms_number)
    rescue
      #失敗した場合ここが動く
      render "signup/sms_authentication"
      return false
    end
    #成功した場合、以下のコードが動き、smsの照合画面へと変遷する
    redirect_to sms_confirmation_signup_index_path
  end
  
  #中略
  private
  
  def profile_params
    #パラメータの項目が多いので割愛、今回は:telに乗せます。
    params.require(:profile).permit(:tel,:address,...)
  end
```
日本の電話番号が正しく入力されることしか想定していないコードとなってしまいました。

次に、smsで受け取った値を入力するページ
<img width="327" alt="スクリーンショット 2019-09-21 0.19.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492745/09084bf3-d06c-e2ec-70ea-2370fef367db.png">

```haml:sms_confirmation.html.haml
    //中略
    = form_for @profile, url: sms_confirmation_signup_index_path,method: :post do |f|
      = label :sms_number, '認証番号'
      = f.text_field :tel, placeholder: '認証番号を入力'
      %p.registration-form-text SMSで届いた認証番号を入力してください
      = f.submit '認証して終了'
```

※見やすさ重視のためクラスや間に入っている要素は削って簡略化しています

ここで入力されて送信された値が以下のアクションsms_checkに飛びます

```ruby:signup_controller.rb
  def sms_check
    @profile = Profile.new
    #送信された値を代入
    sms_number = profile_params[:tel]
    #比較し、一致したら次の登録フォームへ
    if sms_number.to_i == session[:sms_number]
      redirect_to address_signup_index_path
    else
      render "signup/sms_confirmation"
    end
  end

  #中略
  private
  
  def profile_params
    #パラメータの項目が多いので割愛、今回は:telに乗せます。
    params.require(:profile).permit(:tel,:address,...)
  end
```
これで簡易的ですが、smsを送信し、入力を確認することのできるsms認証が実装できました。





………できたのですが、ここで問題が発生。
僕のtwilioアカウントがtrialであったため、僕の携帯電話以外にsmsを飛ばすことができません（飛ばせる番号を増やすことはできますが、番号を登録する必要あり）。

そのため、僕や関係者以外では認証ができないため、今回はコメントアウトしました（泣）


# 最後に
とても簡単なものでしたが、なんとなく思いついたコードでうまく動いて楽しい開発でした。
せっかく書いたのに使えないのが残念ですが、これからもできそうな事は積極的にやっていきたいと思います。

まだまだ粗末な点も多いと思いますが、より良いコード、間違った点などがあればご教授頂けると幸いです。