#270 Authentication in Rails 3.1
- Download:
- source codeProject Files in Zip (185 KB)
- mp4Full Size H.264 Video (8.86 MB)
- m4vSmaller H.264 Video (6.56 MB)
- webmFull Size VP8 Video (7.63 MB)
- ogvFull Size Theora Video (13.2 MB)
Rails 3.1には認証関連の新機能が含まれています。今回のエピソードではそのいくつかについて使い方を紹介します。実際に動作するところを見るために、URLを知っている人なら誰でもアクセスできるページを持つ簡単なアプリケーションを準備しました。
HTTPベーシック認証
このページに参照権限を設定して、特定のユーザしかアクセスできないように制限をかけようと思います。そのためには認証機能を追加しなくてはいけませんが、一番早いのはHTTPベーシック認証を追加する方法です。Rails 3.1ではこのための新しい方法を提供しています。必要な作業は、ページのコントローラを修正して、http_basic_authenticate_with
という新しいメソッドを呼び出すだけです。その時にオプションとして:name
と:password
を指定します。
class SecretController < ApplicationController http_basic_authenticate_with :name => "frodo", :password => "thering" def index end end
認証を一部のアクションのみに制限したい場合には、オプションで:only
か:except
を指定します。言うまでもないですが、実際のアプリケーションではユーザ名とパスワードは素のビューのコード内に持たずに設定ファイル内に移すべきですが、このデモアプリではそのままで進めます。
ページにアクセスしてみるとログインダイアログが表示され、http_basic_authenticate_with
で設定した正しいユーザ名とパスワードを入力しなければページを見ることができなくなりました。
HTTPベーシックはもっと活用されるべきだと思います。サイトの一部へのアクセスに制限をかけたい場合には素早く簡単に設定できるのですが、Rails 3.1ではさらに簡単になっています。
secure_password
を使う
しかし時にはより包括的な、複数ユーザに対応した権限付与のしくみが必要です。 その作業を簡単にするためにRails 3.1にsecure_password
という機能が加わりました。
エピソード250 [動画を見る, 読む]では認証システムをゼロから作りましたが、Rails 3.1ではこの作業がかなり簡単にできるようになりました。今からこの作業を行い、HTTPベーシック認証を置き換えます。
まず最初に、Eメールアドレスとパスワードのフィールドを持つUser
モデルを作成します。
$ rails g model user email:string password_digest:string
次にデータベースのマイグレーションを行い、テーブルを作成します。
$ rake db:migrate
ここで重要なのは、パスワードを格納するフィールドの名称をpassword_digest
にすることです。次にUser
モデルでhas_secure_password
の呼び出しを追加します。
class User < ActiveRecord::Base has_secure_password end
これによって、入力されたパスワードを設定・認証するメソッドと、パスワードと確認パスワードを検証するバリデータと、認証機能が追加されます。前に作成したpassword_digest
フィールドは、裏でパスワードのハッシュ値を保存するのに使われます。
デフォルトではパスワード用のvalidates_presence_of
検証がないため、新規ユーザが作成されたときに起動される検証機能を追加する必要があります。
class User < ActiveRecord::Base has_secure_password validates_presence_of :password, :on => :create end
通常はEメールアドレスも検証するところですが、ここでは省略します。
ユーザによるアカウント作成を許可するためにUsersController
を作成します。
$ rails g controller users
コントローラのコードは次のとおり標準的なものです。
class UsersController < ApplicationController def new @user = User.new end def create @user = User.new(params[:user]) if @user.save redirect_to root_url, :notice => "Signed up!" else render "new" end end end
newビューには、登録用フォームが含まれています。
<h1>Sign Up</h1> <%= form_for @user do |f| %> <% if @user.errors.any?%> <div class="error_messages"> <h2>Form is invalid</h2> <ul> <% for message in @user.errors.full_messages %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :email %> <%= f.text_field :email %> </div> <div class="field"> <%= f.label :password %> <%= f.password_field :password %> </div> <div class="field"> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation %> </div> <div class="actions"><%= f.submit %></div> <% end %>
登録時に確認用パスワードが一致しないと、has_secure_password
からの検証メッセージが表示されます。
確認用パスワードが一致すると登録が成功します。
登録は成功しますがログインはまだなので、次にEメールとパスワードフィールドを持ったログインフォームを作成します。
<h1>Log in</h1> <%= form_tag sessions_path do %> <div class="field"> <%= label_tag :email %> <%= text_field_tag :email, params[:email] %> </div> <div class="field"> <%= label_tag :password %> <%= password_field_tag :password %> </div> <div class="actions"><%= submit_tag "Log in" %></div> <% end %>
このフォームは、リソースであるテーブルデータを編集するためではないので、form_for
ではなくform_tag
を使用しています。フォームはsessions_path
に送信されるので、新たにSessionsController
を作成します。
$ rails g controller sessions
このコントローラは、new
, create
, destroy
アクションを持ち、ユーザのログイン、ログアウトを制御します。
class SessionsController < ApplicationController def new end def create if # authenticated? session[:user_id] = user.id redirect_to root_url, :notice => "Logged in!" else flash.now.alert = "Invalid email or password" render "new" end end def destroy session[:user_id] = nil redirect_to root_url, :notice => "Logged out!" end end
create
アクションはまだ完成しておらず、ログインしたユーザが認証されているかどうかを判断するコードを、追加で記述する必要があります。ここで、Rails 3.1で追加されたsecure_password
を利用することができます。
まず最初に、フォームに入力された値とEメールアドレスが一致するユーザを取得します。そのユーザに対して、has_secure_password
で提供されるauthenticate
メソッドを呼び出して、フォームで入力されたパスワードを渡します。このメソッドが、入力されたパスワードとデータベース内のパスワードダイジェストを比較します。一致するユーザが見つからなければfind_by_email
がnil
を返すので、認証の前にユーザが存在するかどうかをチェックします。
def create user = User.find_by_email(params[:email]) if user && user.authenticate(params[:password]) session[:user_id] = user.id redirect_to root_url, :notice => "Logged in!" else flash.now.alert = "Invalid email or password" render "new" end end
secure_password
で認証する場合に必要なのはこれだけです。これをテストするためにログインしてみます。間違ったパスワードを入力すると、認証が失敗します。
正しい認証情報でログインすると、認証が成功しトップページにリダイレクトされます。
アプリケーションの他の場所では、現在ログイン中のユーザを取得しなくてはいけないので、ApplicationController
にcurrent_user
メソッドを追加し、ビューからアクセスできるようにヘルパーメソッドにします。このメソッドは、セッションから現在のユーザを取得します。
/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base protect_from_forgery private def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end helper_method :current_user end
この認証用メソッドの優れた点は、User
モデルがコード2行のみと非常にシンプルであることです。
class User < ActiveRecord::Base has_secure_password validates_presence_of :password, :on => :create end
これと比べると、エピソード250で紹介した方法ではよりずっと複雑なUser
モデルを使用していました。そうは言うものの、もう一行だけ、attr_accessible
行をモデルに追加して、ユーザ登録フォームからはEメール、パスワード、確認用パスワードのフィールドのみを編集できるようにしておくのがいいでしょう。
class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation has_secure_password validates_presence_of :password, :on => :create end
HTTPSを追加する
アプリケーションで認証を処理する場合、秘密情報を平文で送信したくないので、SSLを用いてHTTPSに切り替えるのがいいでしょう。Rails 3.1の前は、これを行うには手作業かプラグインを介す必要がありましたが、今はずっと簡単に実装する方法があります。
コントローラをHTTPSのみでアクセスされるよう制限をかけるには、ただ単にforce_ssl
クラスメソッドの呼び出しを追加するだけです。さらにコントローラ内のある特定のアクションのみに制限をかけるには、:only
あるいは:except
オプションを用いてbefore_filter
と同じ要領で設定を行えます。
class ApplicationController < ActionController::Base protect_from_forgery force_ssl private def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end helper_method :current_user end
force_ssl
メソッドはテスト(test)と本番(production)モードでのみHTTPSを強制します。アプリケーションのサーバを本番(production)モードで再起動して登録ページを再度読み込むと、SSL版にリダイレクトしようとします。
サーバがHTTPSをサポートしていないのでこれはエラーになりますが、もしサポートしていればこのページのセキュア版が表示されます。
Rails 3.1の認証機能についての今回のエピソードは以上です。認証に関する新しい追加機能によって、Railsアプリケーションに認証機能を実装するのがずっと簡単になりました。