railsforumのrestful_authenticationは素晴らしい!それを見てRESTfulの理解も深まる
最近、restful_authenticationで試行錯誤していて感じたこと。restful_authenticationはユーザー認証に関する必要最小限の機能を提供してくれるが、実際に運用できるレベルに仕上げるには、要点を押さえた的確な修正が必要になる。ユーザー認証のscaffold的な位置付けだろうと。
生成されるソースコードはとても簡潔にまとめられていて、読み易い。しかし、いざ自分好みのログインに修正しようとすると、実にいろいろな手段があり、どのような仕組みにするか本当に迷ってしまう。
例えば、以前の日記で試したパスワード忘れに対応する方法も、今振り返ってみれば最悪の例だ...。アクティベーションとパスワード忘れの処理が混同しているし、修正の手順もセキュリティ的に中途半端。実装の仕方もせっかくのrestful_authenticationのRESTfulなルールを無視している。やはり、自分にはお手本となるコードが必要だ。
そう思って探していると、素晴らしいお手本が見つかった!
ここで紹介されているコードをそのまま実装すれば、以下の機能が提供される。(実際に自分で試したのは、administrator権限によるユーザー管理まで)
単に機能を追加するだけでなく、ログインの手順やページ遷移、その中で表示されるメッセージ、RESTfulなコーディング等すべてが洗練されて実装されている。素晴らしいお手本。
ユーザー登録の手順
- ログインページ
- Sign upリンクをクリックすると...
- ユーザー登録ページへ
- Sign upボタンを押すと...
- ログインページで以下のメッセージ
- 「Thanks for signing up! Please check your email to activate your account before logging in.(サインアップして頂きありがとうございます。ログインする前に、アカウントを有効にするためのメールを確認してください。)」
- サインアップしたユーザーは、この時点ではログインすることは出来ない。まだログアウトの状態だ。
- そしてメールを確認すると、以下の内容で届いている。
件名: [localhost:3000] Please activate your new account
Your account has been created. Username: zarigani Visit this url to activate your account: http://localhost:3000/activate/4eceb24519190ad7cb95eaeb62b1c80508c82ed2
- メールに従ってリンクをクリックすると...
- ログインページで以下のメッセージ
- 「Your account has been activated! You can now login.(アカウントは有効になりました。これでログインできます。)」
- アカウントが有効になったことはメールでも通知される。
件名: [localhost:3000] Your account has been activated!
zarigani, your account has been activated. You may now start adding your plugins: http://localhost:3000/
以上の手順となっている。
restful_authenticationのジェネレーターが生成するオリジナルとの違いは...
- サインアップ直後はログアウト状態。
- #self.current_user = @userをコメントアウトすることで実現されている。
# ---------- app/models/user.rb ---------- class UsersController < ApplicationController ...(中略)... def create cookies.delete :auth_token @user = User.new(params[:user]) @user.save! #Uncomment to have the user logged in after creating an account - Not Recommended #self.current_user = @user flash[:notice] = "Thanks for signing up! Please check your email to activate your account before logging in." redirect_to login_path rescue ActiveRecord::RecordInvalid flash[:error] = "There was a problem creating your account." render :action => 'new' end ...(中略)...
- メールにパスワードを表示しない。
- app/views/user_mailer/signup_notification.html.erbのPassword: <%= @user.password %>を削除している。
<%# ---------- app/views/user_mailer/signup_notification.html.erb ---------- %> Your account has been created. Username: <%= @user.login %>Password: <%= @user.password %>Visit this url to activate your account: <%= @url %>
- ログアウトしない限りログイン状態を維持するRemember meオプションが最初から有効。
- コメントタグ「<!-- Uncomment this if you want this functionality」と「-->」を削除することで実現されている。
<%# ---------- app/views/sessions/new.html.erb ---------- %> <% form_tag session_path do -%> <p><label for="login">Login</label><br/> <%= text_field_tag 'login' %></p> <p><label for="password">Password</label><br/> <%= password_field_tag 'password' %></p> <!-- Uncomment this if you want this functionality<%# この行を削除する %> <p><label for="remember_me">Remember me:</label> <%= check_box_tag 'remember_me' %></p> --><%# この行を削除する %> ...(中略)...
-
- デフォルトの有効期間は2週間だが、user.rbの以下の部分を修正することで調整できる。
# ---------- app/models/user.rb ---------- require 'digest/sha1' class User < ActiveRecord::Base ...(中略)... # These create and unset the fields required for remembering users between browser closes def remember_me remember_me_for 2.weeks end ...(中略)...
パスワードを忘れた場合の手順
- ログインに失敗すると次のメッセージが表示される。「Your username or password is incorrect.(ユーザー名またはパスワードが違っています。)」
- Forgot passwordのリンクをクリックすると...(Forgot passwordのリンクのみ自分で追加)
- ユーザー登録した時のメールアドレスを入力するページに移動する。
- もし間違ったメールアドレスを入力すると次のメッセージが表示される。「Could not find a user with that email address.(そのメールアドレスのユーザーは見つかりませんでした。)」
- メールアドレスが正しく入力されれば...
- ログインページで以下のメッセージ
- 「A password reset link has been sent to your email address.(パスワードを再設定するリンクをお客様のメールアドレス宛てに送信しました。)」
- パスワードを再設定するリンクを記載したメールが届く。
件名: [localhost:3000] You have requested to change your password
zarigani, to reset your password, please visit http://localhost:3000/reset_password/b1e0f1b91da6255640e538d61d58fa0e6b675e6c
- メール記載のリンクをクリックすると、パスワード再設定のページへ移動する。
- パスワードを正しく入力してReset your passwordボタンを押すと...
- ログインページへ移動して次のメッセージが表示される。「Password reset.(パスワードは再設定されました。)」
- その後、パスワードが再設定されたことを知らせるメールも届く。
件名: [localhost:3000] Your password has been reset.
zarigani, Your password has been reset
ユーザー情報の編集
- Todoリストページを求めてログインするとメニューバーに表示されるユーザー情報と共にTodoリストが表示された。
- もし、目指すページがない時は、以下のユーザー名Zariganiのリンクページが表示される。
- スタイルシートは未設定なので、メニューバーは一般的なリストとして表示されている。
- 順にリンクを辿ってみると...
- ユーザー名Zariganiのリンクページ
- Edit Profileのリンクページ
- Change Passwordのリンクページ
- Log Outするとメッセージが表示される。「You have been logged out.(ログアウトしました。)」
アクセス権による制限とadministrator権限によるユーザー管理
- zariganiでログインして、http://localhost:3010/usersへアクセスしてみると...
- ルートページhttp://localhost:3010/へリダイレクトされてアクセスできない。
- メッセージは「You don't have permission to complete that action.(そのアクションを実行する権限がありません。)」
- adminでログインしてみる。(パスワード: admin)
- メニューにAdminister Usersのリンクが追加されているのでクリックすると...
- ユーザーリストが表示されて以下の設定が可能だ。
- Enabled?... そのユーザーの有効・無効の設定(無効だとログインできないユーザー)
- Roles... アクセス権限の設定
- デフォルトではユーザーadminに、Administrator権限が設定されている状態
- ユーザーzariganiについては、アクセス権限なしの状態
何が起こっているのか?
- UsersControllerでは、before_filter :check_administrator_role... が実行されて、index, destroy, enableアクションについては、ユーザーがadministrator権限を持っていることを要求している。
- not_logged_in_requiredはログインしていないことを要求する。
- login_requiredはログインしていることを要求する。
# ---------- app/controllers/users_controller.rb ---------- class UsersController < ApplicationController layout 'application' before_filter :not_logged_in_required, :only => [:new, :create] before_filter :login_required, :only => [:show, :edit, :update] before_filter :check_administrator_role, :only => [:index, :destroy, :enable] ...(中略)...
- ユーザーzariganiにはアクセス権の設定はない状態
- ユーザーadminについては、マイグレーションファイルでadministrator権限が設定されている状態
# ---------- db/migrate/20080801140457_create_permissions.rb ---------- class CreatePermissions < ActiveRecord::Migration def self.up create_table :permissions do |t| t.integer :role_id, :user_id, :null => false t.timestamps end #Make sure the role migration file was generated first Role.create(:rolename => 'administrator') #Then, add default admin user #Be sure change the password later or in this migration file user = User.new user.login = "admin" user.email = "info@yourapplication.com" user.password = "admin" user.password_confirmation = "admin" user.save(false) user.send(:activate!) role = Role.find_by_rolename('administrator') user = User.find_by_login('admin') permission = Permission.new permission.role = role permission.user = user permission.save(false) end def self.down drop_table :permissions Role.find_by_rolename('administrator').destroy User.find_by_login('admin').destroy end end
新たに独自の権限moderatorを設定したくなったら...
- rolesテーブルに、name = "moderator"のレコードを追加する。(以下はマイグレーションを利用する方法)
# ---------- db/migrate/20080801140457_create_permissions.rb ---------- class CreatePermissions < ActiveRecord::Migration def self.up create_table :permissions do |t| t.integer :role_id, :user_id, :null => false t.timestamps end ...(中略)... Role.create(:rolename => 'moderator') ...(中略)...
- moderatorを要求するメソッドcheck_moderator_roleを追加する。
# ---------- lib/authenticated_system.rb ---------- module AuthenticatedSystem protected ...(中略)... def check_moderator_role check_role('moderator') end ...(中略)...
- これで before_filter :check_moderator_role とすれば、moderator権限が要求されることになる。
ユーザーの状態によるusersテーブルの変化
- 操作によってユーザーの状態が変化する時、usersテーブルのフィールドは、抜粋すると以下のように変化していく。
イベント | remember_token | remember_token_expires_at | activation_code | activated_at | password_reset_code | enabled |
---|---|---|---|---|---|---|
新規ユーザー登録直後 | 07db98435f337e7acbe5b485dc5eebed4a30c2ec | t | ||||
アクティベーション完了 | 07db98435f337e7acbe5b485dc5eebed4a30c2ec | 2008-07-28 02:57:05 | t | |||
パスワードリセット依頼 | 07db98435f337e7acbe5b485dc5eebed4a30c2ec | 2008-07-28 02:57:05 | b1e0f1b91da6255640e538d61d58fa0e6b675e6c | t | ||
パスワードリセット完了 | 07db98435f337e7acbe5b485dc5eebed4a30c2ec | 2008-07-28 02:57:05 | t | |||
Remember meチェックありのログイン | c22dea3efeacbf022ba6902ee6a3273df800493c | 2008-08-18 04:37:06 | 07db98435f337e7acbe5b485dc5eebed4a30c2ec | 2008-07-28 02:57:05 | t | |
ログアウト | 07db98435f337e7acbe5b485dc5eebed4a30c2ec | 2008-07-28 02:57:05 | t | |||
administrator権限によるdisable | 07db98435f337e7acbe5b485dc5eebed4a30c2ec | 2008-07-28 02:57:05 | f | |||
administrator権限によるenable | 07db98435f337e7acbe5b485dc5eebed4a30c2ec | 2008-07-28 02:57:05 | t |
RESTfulのルールに従ったコントローラー構成
- app/controllers/以下にはrestful_authenticationを制御する5つのコントローラーが定義されている。
- 唯一の例外users_controller.rbのenableアクションを除いて、アクションメソッドとして定義されるのはindex、new、create、show、edit、update、destroyのみ。
- ほぼ完全にRESTfulのルールを守っている。
- コントローラーは以下の役割を担っている。
- sessions_controller.rb: ログイン(new, create)、ログアウト(destroy)
- users_controller.rb: 新規ユーザー登録(new, create)、ユーザー情報の修正(edit, update)、ログイン情報の表示(show)、administrator権限によるユーザーリストの取得・有効無効の設定。
- accounts_controller.rb: アクティベーション(show)、ログイン状態でのパスワード変更(edit, update)
- passwords_controller.rb: Forgot Passwordのページ(new)、Reset Passwordボタンの処理(create)、メール記載のForgot Passwordのページ(edit)、Rset Your Passwordボタンの処理(update)
- roles_controller.rb: ユーザー別のアクセス権リスト(index)、ユーザーにアクセス権を追加する(update)、ユーザーからアクセス権を削除する(destroy)
- RESTfulな設計とはこのようにするのかと、思い知らされた。
- map.resourcesで:collectionや:memberオプションを多用し始めたら、負け組。
- そんな時は新たなくくりでコントローラーを抜き出して、index、new、create、show、edit、update、destroyアクションで置き換えられないか考えて直してみる。
- 例えばここでは、UsersControllerのactivateアクションは、AccountsControllerのshowアクションに置き換えられている。
ビューを整えて、日本語訳すれば、そのまま使えそうな感じだ!これでlogin_engine、user_engineから移行することができる。