ブログ

2018.04.02

Authy を使った 2段階認証の実現

こんにちは、本間です。

authy

コミュニケーション API である Twilio では、 Authy という2段階認証のためのサービスを提供しています。

2段階認証とは、メールアドレス/パスワードの認証とさらにもう一つの認証方法を付け加えることで、パスワードが漏洩しても問題が起きないようにするためのセキュリティ向上機能です。例えば、メールアドレス/パスワードの認証が通った後に SMS や電話で特定の数字を入力したり、専用アプリを使って表示された番号を入力するなどの方法が一般的です。

本記事では Authy の機能概要と、実際に Authy を使った実装例について Ruby on Rails のサンプルとともにご紹介します。

Authy の利用検討

今までの Authy では、SMS/電話 発信の2段階認証と、専用 Authy アプリでの2段階認証を提供していました。基本機能としては完備していたのですが、Authy 自体が英語による案内がデフォルトの実装となっており、これらメッセージを日本語にするには Authy サポートに英語で問い合わせるなどの大きな障壁がありました。また、2段階認証のAuthy アプリも、英語しか存在せず、日本人が2段階認証として Authy を使うというのがあまり一般的ではないことも、大きな課題としてありました。

しかし、先日 Authy は大きなアナウンスを出しました。Authy のプラットフォームを使って、Google Authenticator などの他の2段階認証アプリにも対応することを発表しました。 私たち日本人にとっては、Google Authenticator で2段階認証をすることは一般的になりつつあります。もちろん Google Authenticator は日本語に対応してますので、新しく2段階認証を始める方にもおすすめしやすいアプリです。

Authy アプリで出来たことをそのまま Google Authenticator でもできるということではないので注意してください。 Authy API で Authy アプリを使えば Authy API で携帯電話番号を登録した瞬間に、Authy アプリで自社サービスの認証を新規で自動登録してくれます。また、Authy One Touch という優れものもあります。これは、ログイン時に2段階認証時に数字を入力してもらうのではなく、Authy アプリからプッシュ通知されたアラートを許可するだけで2段階認証が通るものです。ユーザーの2段階認証の手間を大幅に削減できる魅力があります。
反対に、Authy API で Google Authenticator と連携させるためには Authy API から発行された QR コードを Google Authenticator で読み込んで登録してもらうフローが必要になります。

私自身 Authy の利用を長らく検討してきました。Authy One Touch の便利さは大変魅力に感じていたのですが、いかんせんアプリが英語しかサポートしていないというのと日本でのシェアがあまりないことを考えると、Authy 自体の導入を見送りせざるを得ませんでした。
ということで、以下では Google Authenticator 等の外部の2段階認証アプリを Authy で利用するための方法についてご紹介します。

Authy 実装

それでは 2段階認証の実装の詳細に移っていきます。まずは Authy を利用する流れについてご紹介します。

1. Authy ユーザーの登録

まずは Authy API の利用に必要な Authy API Key を発行しましょう。なぜか現時点での Authy では、Twilio マスターアカウントを使って 外部の2段階認証アプリを連携することができません。必ず Authy 用のサブアカウントを作って、そこに表示された Authy API Key を利用するようにしましょう。また、Authy の設定で Generic authenticator tokens を ON にする必要があります。

続いて Authy API で Authy ユーザーを登録します。この登録に必要な情報は、メールアドレス、携帯電話番号、国コードです。

以下のサンプルでは、HTTP リクエストを送る Ruby Gem である Faraday を利用しています。公式 Authy Gem がありますが、現時点では この Gem はQR コードの発行に対応していないため、純粋な HTTP リクエストでの実装となっています。

      res = Faraday.post do |req|
        req.url "#{AUTHY_API_BASE}/protected/json/users/new"
        req.params = { user: {
          email: self.email,
          cellphone: mobile_phone,
          country_code: "81"
        }}
        req.headers['X-Authy-API-Key'] = ENV['AUTHY_API_KEY']
      end
      res = JSON.parse(res.body)
      raise res["message"] unless res["success"]
      res["user"]["id"] if res["success"]

これで、Authy ID を取得することができます。これを使って、2段階認証の On/Off の設定に移っていきます。

2. QRコードの発行

登録したユーザーが Google Authenticator などで利用できるようにするための QR コードを発行します。そのためには先ほど生成した Authy ID を使って発行します。

    def generate_qr authy_id
      res = Faraday.post do |req|
        req.url "#{AUTHY_API_BASE}/protected/json/users/#{authy_id}/secret"
        req.params = {
          label: "#{self.email}",
          qr_size: 170
        }
        req.headers['X-Authy-API-Key'] = ENV['AUTHY_API_KEY']
      end
      res = JSON.parse(res.body)
      raise res["message"] unless res["success"]
      res["qr_code"] if res["success"]
    end

これで QR コードの URL が渡ってくるので、<img>でユーザーに表示します。

3. 入力の検証

正しく QR コードで読み取っていただいた後、最初に Google Authenticator で表示される数値の検証をして、正しく入力されて初めて2段階認証を有効化するのが良いとされています。Authy ID と入力されたコードを Authy API で検証してもらいましょう。

    def verify_code authy_id, token
      token = URI.encode(token.gsub(/\s+/, ""))
      res = Faraday.get do |req|
        req.url "#{AUTHY_API_BASE}/protected/json/verify/#{token}/#{authy_id}"
        req.headers['X-Authy-API-Key'] = ENV['AUTHY_API_KEY']
      end
      res.status
    end

200のステータスが返って来れば、無事 Authy ID と携帯電話番号を保存して次回以降のログインでは2段階認証を表示するように実装します。

認証時の2段階認証コード入力

さて、いよいよログイン時に2段階認証をしてもらうステップに移ります。

Authy とは関係ないのですが、Ruby on Rails を使っている場合はここが一番の難関です。Ruby on Rails でよく使われる認証の Gem である Devise は、この2段階認証の実装を想定された作りになっていません。公式 Authy Gem では、Devise をハッキングしてなんとかしている部分があります。今回は Authy Gem を読みながら今回のケースにのみ最適化したコードをご紹介します。

  # config/routes.rb
  devise_for :users, controllers: {
    sessions: 'sessions'
  }

# sessions_controller.rb
class SessionsController < Devise::SessionsController
  before_action :check_request_and_redirect_to_verify_token, :if => :is_signing_in?

  private
  def is_signing_in?
    devise_controller? && signed_in?(resource_name) &&
        is_devise_sessions_controller? &&
        self.action_name == "create"
  end
  def is_devise_sessions_controller?
    self.class == Devise::SessionsController || self.class.ancestors.include?(Devise::SessionsController)
  end

  def check_request_and_redirect_to_verify_token
    if signed_in?("user") && current_user.authy_enabled
      id = current_user.id
      sign_out
      session["user_id"] = id
      redirect_to console_users_verify_url
    end
  end
end

ここで重要なのは、ログイン成功後に Authy が有効になっている場合には一度ログアウトしてユーザーIDだけセッションに保存しておくことです。当たり前ですがログイン成功後に 2段階認証の画面を表示するだけでは、その時点でログイン後の URL を入力するだけで該当ページへ飛べてしまうので2段階認証をする意味がなくなってしまいます。

  def verify
    user = User.find(session["user_id"])
    res = user.verify_code(user.authy_id, params[:check_code])
    if res == 200
      sign_in(user)
      flash['notice'] = "ログインしました。"
      redirect_to console_url
    else
      flash['alert'] = "正しいコードが確認できませんでした。"
      redirect_to console_users_verify_url
    end
  end

これで無事に 2段階認証が通った場合のみ専用ページに飛べるようになりました。

終わりに

本記事では Authy を使って Google Authenticator の2段階認証を実装する方法をご案内しました。

今後、Authy のアプリ自体が日本でも普及していけば One touch などの便利な機能を利用できますが、それまでは外部の Google Authenticator を利用しながら動向を見守るようにしています。

2段階認証による認証は、一般的になりつつあります。ぜひこの機会に 2段階認証の実装を検討してみてはいかがでしょうか。