homeASCIIcasts

209: Deviseの導入 

(view original Railscast)

Other translations: En Es It Cn

Other formats:

Written by Naomi Fujimoto

今までのエピソードでいくつかの認証の手法について解説してきましたが、今回もひとつ紹介します。今回紹介するのはdeviseという名前で、最近特に人気が出てきています。Deviseは、Rackベースの認証ツールであるWardenの上で、それを隠した状態で動作します。今回のエピソードを理解するためには、直接deviseを操作するので、Wardenに関する知識は必要ありません。

Deviseはフルスタックを使って認証を扱います。エピソード160 [動画を見る, 読む]で紹介したAuthlogicを触ったことがあれば、それがモデル層だけでなりたっていることをご存知でしょう。一方deviseはRails Engineとして動作して、コントローラとビューも持っています。Deviseはモジュラー方式で現在は11のモジュールから成り立っていて、それぞれが違った認証機能を提供します。例えばモジュールの一つのRememberableは保存したクッキーにユーザの認証情報を記憶し、別のモジュールのRecoverableはユーザのパスワードリセットを世話したりリセット作業の指示を送信します。このアプローチによって、アプリケーションで使いたい認証パーツを簡単に選べるようになっています。

アプリケーションに認証機能を追加する

アプリケーションでdeviseを動かすために何が必要かを見ていきましょう。このスクリーンショットは簡単なプロジェクト管理用アプリケーションです。Rails 3.0で書かれており、deviseを使ってUserモデルと認証機能を追加していきます。

プロジェクト管理用アプリケーション

DeviseはRails 3で動作しますが、正しいバージョンを設定するために従わなくてはいけないインストール手順がいくつかあります。Rails 3アプリケーション用には、最新バージョン(現在は1.1.rc0)をインストールします。deviseをRails 2.3のアプリケーションで使用したい場合はバージョン1.0.6が必要です。これは、バージョン1.1がRails 2に対して下位互換性を持っていないからです。

Rails 3アプリケーションでは、Gemfileにdeviseの参照情報を追加して、正しいバージョンを特定します。

/Gemfile

gem 'devise', '1.1.rc0'

準備ができたらbundleコマンドを実行して、gemとその依存関係をインストールします。

bundle install

次はインストール用のジェネレータを実行します。

$ rails generate devise_install
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml

===============================================================================

Some setup you must do manually if you haven't yet:

  1. Setup default url options for your specific environment. Here is an
     example of development environment:

       config.action_mailer.default_url_options = { :host => 'localhost:3000' }

     This is a required Rails configuration. In production is must be the
     actual host of your application

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root :to => "home#index"

===============================================================================

このコマンドによっていくつかのファイルが生成されます。まず初期設定ファイルと、deviseが表示するすべてのメッセージを含んだlocaleファイルです。その下には、手作業で行う2つの設定作業が記述されています。一つ目にアプリケーションのメーラにhostオプションを設定します。二つ目にルート(root)のルート(route)設定を持たなくてはいけません。アプリケーションにはrootのルートがすでにあるのでそれについては作業は不要ですが、メーラの設定は必要です。これについては、上記の手順の表示から行をコピーして、開発(development)モードの環境ファイルのブロック内に挿入します。

/config/environments/development.rb

config.action_mailer.default_url_options = { :host => 'localhost:3000' }

この行は、localhosthostオプションを追加しています。アプリケーションを本番(production)モードで稼働するときには、対応するproduction.rbファイルのドメイン名にこの値を設定します。

deviseユーザモデルを作成する

認証処理を行うためにUserモデルが必要になりますが、deviseはそのためのジェネレータ(generator)を提供します。これを使うことは必須ではないですが、利用すればdeviseの設定で何ステップかを省略できます。

$ rails generate devise User
      invoke  active_record
      create    app/models/user.rb
      invoke    test_unit
      create      test/unit/user_test.rb
      create      test/fixtures/users.yml
      inject  app/models/user.rb
      create  db/migrate/20100412200407_devise_create_users.rb
       route  devise_for :users

このジェネレータはいくつか興味深いものを作成します。モデルファイル、マイグレーション、 devise_forルートです。これらを順に見ていきましょう。

生成されたモデルファイルの中身は以下のとおりです。

/app/models/user.rb

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :lockable, :timeoutable and :activatable
  # :confirmable
  devise :database_authenticatable, :registerable, 
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation
end

Userモデルは通常のActiveRecordモデルに非常に似ていますが、deviseメソッドの呼び出しが含まれています。これによって認証マジックが実現します。deviseメソッドは、引数としてアプリケーションでサポートするモジュールのリストをとります。先に説明した:rememberable:recoverableが含まれています。このリストから簡単にモジュールを追加や削除することで、deviseの認証機能をアプリケーションのニーズに従ってカスタマイズできます。その目的で、ユーザが登録したことをEメールで確認しなくてもいいように:confirmableを削除しました。

Userクラスにもattr_accessibleがあり、ユーザが画面から修正できる属性をリストアップします。もしモデルにカスタムカラムがある場合は、ここで追加できます。

次に、生成されたマイグレーションファイルを見てみましょう。

class DeviseCreateUsers < ActiveRecord::Migration
  def self.up
    create_table(:users) do |t|
      t.database_authenticatable :null => false
      # t.confirmable
      t.recoverable
      t.rememberable
      t.trackable
      # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both

      t.timestamps
    end

    add_index :users, :email,                :unique => true
    # add_index :users, :confirmation_token,   :unique => true
    add_index :users, :reset_password_token, :unique => true
    # add_index :users, :unlock_token,         :unique => true
  end

  def self.down
    drop_table :users
  end
end

このファイルは簡単に理解することができるでしょう。テーブルに各モジュール用に必要なカラムを作成する別のメソッド呼び出しが含まれています。confirmableモジュールは使用しないため、対応するメソッドをコメントアウトしました。合わせて、データベースのテーブルにカラムが存在しないので、confirmation tokenに関連するインデックスを削除します。

マイグレーションファイルを利用するモジュールに合わせて修正したら、データベースマイグレーションを実行します。

rake db:migrate

最後に、routeルートファイルに追加されたdevise_forルートが設定されました。rake routesを実行すると、このコードが生成するルートが表示されます。

    new_user_session   GET    /users/sign_in                 {:controller=>"devise/sessions", :action=>"new"}
          user_session POST   /users/sign_in                 {:controller=>"devise/sessions", :action=>"create"}
  destroy_user_session GET    /users/sign_out                {:controller=>"devise/sessions", :action=>"destroy"}
                       POST   /users/password(.:format)      {:controller=>"devise/passwords", :action=>"create"}
         user_password PUT    /users/password(.:format)      {:controller=>"devise/passwords", :action=>"update"}
     new_user_password GET    /users/password/new(.:format)  {:controller=>"devise/passwords", :action=>"new"}
    edit_user_password GET    /users/password/edit(.:format) {:controller=>"devise/passwords", :action=>"edit"}
                       POST   /users(.:format)               {:controller=>"devise/registrations", :action=>"create"}
                       PUT    /users(.:format)               {:controller=>"devise/registrations", :action=>"update"}
     user_registration DELETE /users(.:format)               {:controller=>"devise/registrations", :action=>"destroy"}
 new_user_registration GET    /users/sign_up(.:format)       {:controller=>"devise/registrations", :action=>"new"}
edit_user_registration GET    /users/edit(.:format)          {:controller=>"devise/registrations", :action=>"edit"}

これは少し読みにくいですが、いくつかの認証のためのルート(ログイン、ログアウト、パスワードの再設定、新規ユーザの登録、プロフィールの再設定)が含まれています。これらのルートを変更したい場合はすべてカスタマイズ可能です。

これらのルートを介して認証機能にアクセスできるようになりました。/users/sign_upにアクセスすると、新規ユーザ登録用のフォームが表示されます。

deviseが生成する登録フォーム

フォームに記入して「登録(Sign up)」ボタンをクリックすると、登録が完了してログインします。ログイン状態のときには/users/sign_outにアクセスすることでログアウトできます。再度ログインするために/users/sign_inにアクセスして、登録時に指定したユーザ名とパスワードをフォームに入力するとエラーが表示されます。

Rails 3 beta 2で登録処理がエラーを返す

これはRails 3.0 beta 2の問題により発生するもので、deviseに特有のものではありません。このエラーが発生しても、幸い修正は簡単です。/config/initializers/cookie_verification_secret.rbのファイルに、署名されたcookieを検証する秘密鍵を設定するためのコード行があります。

/config/initalizers/cookie_verification_secret.rb

# Be sure to restart your server when you modify this file.

# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!# Make sure the secret is at least 30 characters and all random, 
# no regular words or you'll be exposed to dictionary attacks.
Rails.application.config.cookie_secret = '3b161f7668f938d1aeb73e1137964f8d5ebaf32b9173c2130ecb73b95b610702b77370640dce7e76700fb228f35f7865ab2a5ccd22d00563504a2ea9c3d8dffe'

この行を/config/application.rbに移動し、Rails.applicationセクションをこのファイルから削除するだけです。

/config/application.rb

require File.expand_path('../boot', __FILE__)
require 'rails/all'

Bundler.require(:default, Rails.env) if defined?(Bundler)

module ProjectManage
  class Application < Rails::Application
    config.filter_parameters << :password
    config.cookie_secret = '3b161f7668f938d1aeb73e1137964f8d5ebaf32b9173c2130ecb73b95b610702b77370640dce7e76700fb228f35f7865ab2a5ccd22d00563504a2ea9c3d8dffe'
  end
end

この修正を有効化するためにサーバを再起動する必要がありますが、これで正しくログインできるようになりました。

登録が成功する

正常動作する認証機能が設定できたので、ここから改良を加えていきます。ページの上部に、ログイン中にはログアウト、ログアウト後はログインのためのリンクを表示して、現在の状態がわかるようになっていたら便利でしょう。

アプリケーションのレイアウトファイルにこの修正を加えればすべてのページにリンクを表示できます。フラッシュメッセージを表するコードの直前に次のコード行を追加します。

/app/views/layouts/application.html.erb

<div id="user_nav">
  <% if user_signed_in?%>
    Signed in as <%= current_user.email %>. Not you?    <%= link_to "Sign out", destroy_user_session_path %>
  <% else %>
    <%= link_to "Sign up", new_user_registration_path %> or
    <%= link_to "Sign in", new_user_session_path %>
  <% end %>
</div>

コードにはif/else構文があり、サイトを見ているユーザがログイン中かどうかによって、異なったメッセージを表示できるようにしています。これを判断するために、deviseが提供するuser_signed_in?メソッドを呼び出します。現在のユーザがログイン中の場合はtrueを返します。現在ログイン中のユーザが存在する場合、ログインに使用したメールアドレスとログアウト用のリンクを表示します。Eメールアドレスを表示するためには、current_userを呼び出して現在のユーザのUserオブジェクトを取得してEメール属性を表示します。ログアウト用リンクの正しいパスを得るために、rake routesを実行して前に作成したルートのリストを見直すことができます。ここにリストされたルートの中にdestroy_user_sessionがあり、/users/sign_outにマップされているので、destroy_user_session_pathを用いてリンク用の正しいURLを設定します。

同じようにnew_user_registration_pathnew_user_session_pathを用いて登録とログイン用のリンクを設定します。これらが正しく設定されたらページを再度読み込みます。するとページの上部にユーザの情報が表示されます。

ユーザの詳細が各ページの上部に表示される

「ログアウト」のリンクをクリックすると、代わりに「登録」と「ログイン」のリンクが表示されます。

ログインしていない場合は「登録」と「ログイン」のリンクが表示される

ここまでで示した通り、deviseを使えばかなり容易に完全な認証機能を設定することができます。少しの設定で新規ユーザに登録をさせる機能とユーザがログインとログアウトを行う機能を持つことができました。他にもまだ触れていない部品があります。例えば、パスワードのリセットページです。もしconfirmableモデルを利用する設定にしていたら、deviseがフォームと関連ロジックを自動的に作っていたはずです。

パスワードリセットのページ

これらのフォームが自動的に生成されるのは便利ですが、アプリケーションの外観に合わせるためにカスタマイズする必要が出てくるでしょう。幸いdeviseを使えば簡単に行うことができるので、次回のエピソードで紹介します。