アプリなどを開発するブログ

React Native / Swift / Ruby on Railsなどの学習メモ。


【Devise3.2 + Rails4】authentication_tokenでiOSからユーザー認証

iOSとRuby on Rails4 + Deviseで、
ユーザー認証機能を持ったiOSアプリを作ろうとしたら、
Deviseからauthentication tokenが消えていました。

どうやら3.1から消えたみたい?
Deviseの開発チームのコメントによると、
このやり方はセキュアじゃないので、Deviseのユーザーの選択肢から消すために
dupricatedにした、とのこと。

じゃあどうすればいいんやろ。と思ってたら実装方法書いたGistを発見。

#josevalim / 1_unsafe_token_authenticatable.rb

https://gist.github.com/josevalim/fb706b1e933ef01e4fb6

こんな感じ。

前提 : DeviseでUserモデルを作成済

Userモデルにauthentication_tokenカラムを追加

rails g migration AddAuthenticationTokenToUser authentication_token

class User < ActiveRecord::Base
  before_save :ensure_authentication_token
 
  # authentication_tokenが無い場合は生成して設定
  def ensure_authentication_token
    if authentication_token.blank?
      self.authentication_token = generate_authentication_token
    end
  end
 
  private
  
  def generate_authentication_token
    loop do
      token = Devise.friendly_token
      break token unless User.where(authentication_token: token).first
    end
  end
end

ApplicationControllerをオーバーライドする

1. セキュアじゃない例
class ApplicationController < ActionController::Base
  # Deviseの認証ヘルパーメソッドの前にこのメソッドを呼ぶ
  before_filter :authenticate_user_from_token!

  # Deviseの認証ヘルパーメソッド
  before_filter :authenticate_user!
 
  private
  
  # この例はシンプルに、パラメータからtoken authenticationを取得している。
  # が、ヘッダからtokenを取得する形にしてもOK
  def authenticate_user_from_token!
    user_token = params[:user_token].presence
    user = user_token && User.find_by_authentication_token(user_token.to_s)
 
    if user
      # store: falseにしているので、  
      # セッション内にユーザーは保存されず、リクエストする度にトークンが毎回必要。  
      # サインインtokenみたいな働きをするtokenが欲しければ、store: false を記述しなければ良い

      sign_in user, store: false
    end
  end
end
2. セキュアな例
class ApplicationController < ActionController::Base

  before_filter :authenticate_user_from_token!
  before_filter :authenticate_user!
 
  private
  
  def authenticate_user_from_token!
    user_email = params[:user_email].presence
    user = user_email && User.find_by_email(user_email)
 
    # タイミング攻撃を防ぐためにトークンの照合にDevise.secure_compareを使う
    if user && Devise.secure_compare(user.authentication_token, params[:user_token])
      sign_in user, store: false
    end
  end
end

タイミング攻撃?

タイミング攻撃(timing attack)とは