#283 Authentication with Sorcery
- Download:
- source codeProject Files in Zip (94.3 KB)
- mp4Full Size H.264 Video (24.6 MB)
- m4vSmaller H.264 Video (12.8 MB)
- webmFull Size VP8 Video (16 MB)
- ogvFull Size Theora Video (30.1 MB)
エピソード250[動画を見る, 読む]で、Railsアプリケーションにゼロから認証機能を追加しました。既存のサードパーティ製のツールを使いたい場合は、一部の機能を自動化してくれるgemがいくつかありますが、今回のエピソードではSorceryを紹介します。
Sorceryはシンプルなツールです。わずか20のメソッドを持つだけですが、およそ必要な認証機能のすべてを提供します。そのシンプルさに反してフル機能を持ち、またモジュール方式をとっているため、例えばパスワードの初期化、活動ログなど、必要な部品のみを選択して有効化できます。Sorceryは他の認証用gemと比較するとより低いレベルで動作し、コントローラ層やビュー層のコーディングは開発者にゆだねられています。今回のエピソードではこのSorceryを使って既存のRailsアプリケーションに認証機能を付加していきます。
はじめに
対象とするアプリケーションはいたってシンプルです。ウェルカム画面を持っていて、そこには秘密のページへのリンクがあります。現時点では秘密のページは誰でも見ることができますが、ログインしたユーザのみがアクセスできるように制限をかけようと思います。このためにはアプリケーションに認証機能を追加する必要があり、ここでSorceryが登場します。
Sorceryはgemとして提供され、通常の方法でインストールを行います。Gemfile
に参照情報を追加し、bundle
コマンドを実行します。
gem 'sorcery'
Bundlerが終了したら、Sorceryの初期化ファイルを追加するために次のコマンドを実行します。(この詳細は後ほど説明します。)
$ rake sorcery:bootstrap
次にsorcery_migration
を作成します。これを使って、追加したいSorceryモジュールを選択します。ここでは、単純なパスワード認証に必要なcore
モジュールと、remember_me
モジュールをインクルードします。すべてのモジュールのリストを参照するためには、SorceryのREADMEをチェックしてください。
$ rails g sorcery_migration core remember_me create db/migrate/20110914221626_sorcery_core.rb create db/migrate/20110914221627_sorcery_remember_me.rb
コマンドを実行すると、選択したモジュールに応じていくつかのmigrationファイルが生成されます。sorcery_core
のmigrationを見ると、新しいusers
テーブルに追加される属性が確認できます。
class SorceryCore < ActiveRecord::Migration def self.up create_table :users do |t| t.string :username, :null => false t.string :email, :default => nil t.string :crypted_password, :default => nil t.string :salt, :default => nil t.timestamps end end def self.down drop_table :users end end
デフォルトではmigrationはusername
フィールドを作成します。username
フィールドはemail
フィールドと同じく必要ないため、該当行をコメントアウトします。
class SorceryCore < ActiveRecord::Migration def self.up create_table :users do |t| # t.string :username, :null => false t.string :email, :default => nil t.string :crypted_password, :default => nil t.string :salt, :default => nil t.timestamps end end end
Sorceryの設定を変更して、 username
の代わりにemail
を使用するよう指定します。このためにSorceryの初期化ファイルを修正します。このファイルの最初で、有効化したいモジュールを特定します。core
モジュール以外で使用するのはremember_me
モジュールだけですが、ここでそれを追加します。
/config/initializers/sorcery.rb
# The first thing you need to configure is which modules you need in your app. # The default is nothing which will include only core features (password encryption, login/logout). # Available submodules are: :user_activation, :http_basic_auth, :remember_me, # :reset_password, :session_timeout, :brute_force_protection, :activity_logging, :external Rails.application.config.sorcery.submodules = [:remember_me] # Rest of file omitted.
他にもここで指定できる設定オプションがあり、それらはファイル内でわかりやすく説明されています。ほとんどは変更する必要はありませんが、ひとつだけusername_attribute_name
は変更する必要があります。このオプションを有効化して、その値をユーザを特定するのに使うフィールドである:email
に変更します。
config.user_config do |user| # -- core -- user.username_attribute_name = :email # change default username # attribute, for example, # to use :email as the login. # Other options omitted. end # This line must come after the 'user config' block. config.user_class = "User" # define which model authenticates # with sorcery. end
ファイルの最後には、Sorceryが認証に利用するモデルの名前を指定するための設定項目があり、デフォルトではUser
になっています。このアプリケーションにはまだUser
モデルがないため、ここで作成することにします。すでにUser
のフィールドを定義するmigrationファイルがあるので、Railsに対してモデル作成時にそれを生成しないように指定します。
$ rails g model user --skip-migration
User
モデルでSorceryを有効化するために、コードを一行追加します。
class User < ActiveRecord::Base authenticates_with_sorcery!end
これにより、User
モデルで認証を扱えるようにいくつかのメソッドが追加されます。しかし、検証(validation)や属性を保護する機能は追加されません。それらを追加するかどうかは開発者にゆだねられます。
class User < ActiveRecord::Base authenticates_with_sorcery! attr_accessible :email, :password, :password_confirmation validates_confirmation_of :password validates_presence_of :password, :on => :create validates_presence_of :email validates_uniqueness_of :email end
ここでmigrationを実行し、usersテーブルを作成します。
$ rake db:migrate
コントローラとビューを追加する
User
モデルの準備ができたので、対応するコントローラを設定します。まず登録手続きを処理するUsersController
を作成します。
$ rails g controller users new
ログイン処理をおこなうSessionsControllerも作成します。
$ rails g controller sessions new
これは、エピソード250で認証機能をゼロからに作ったときの作業に似ているので、ここは簡単に触れるだけにします。UsersController
は標準的なもので、new
アクションでUser
を新規作成し、 create
アクションでは渡されたパラメータからUser
を新規作成します。
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
テンプレートも標準的なもので、新規ユーザを作成するフォームにemail
、password
、password_confirmation
の各フィールドと、検証エラーを表示するためのコードが含まれています。
<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 %>
SessionsController
の中身は、より興味深いものになっています。new
アクションがありますがコードを追加する必要がないので、次にテンプレートを見てみます。ここでは、簡単なログインフォームで、email
とpassword
のためのテキストフィールドとremember_me
フィールドのためのcheckbox
を持ったものを作ります。
<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="field"> <%= check_box_tag :remember_me, 1, params[:remember_me] %> <%= label_tag :remember_me %> </div> <div class="actions"><%= submit_tag "Log in" %></div> <% end %>
ログインフォームを処理するためのcreate
アクションを作成します。Sorceryではlogin
というメソッドが提供され、 3つのパラメータをとります。ユーザ名かメールアドレス、入力されたパスワード、remember_me
チェックボックスの値です。このメソッドが認証を行い、一致するものが見つかった場合にそのUser
を返します。これを確認して、ユーザが見つかったらトップページにリダイレクトします。見つからなかったら、フラッシュメッセージと共にログインフォームを再度表示します。
class SessionsController < ApplicationController def new end def create user = login(params[:email], params[:password], ↵ params[:remember_me]) if user redirect_back_or_to root_url, :notice => "Logged in!" else flash.now.alert = "Email or password was invalid." end end end
ユーザが見つかった場合にトップページにリダイレクトさせるときに、redirect_to
を使う代わりに、Sorcery が提供するredirect_back_or_to
というメソッドを使います。このメソッドはredirect_to
と同じように振るまいますが、SorceryがURLを保存している場合は、コードで指定したURLではなく保存されたURLにリダイレクトします。これで便利なのは、あるページにアクセスしたユーザをまずログインさせたいという場合に、Sorceryがそのユーザをログインページに誘導し、ログインが成功したら最初に訪れようとしていたページに戻してくれる点です。
ユーザがログアウトする手段も提供しなくてはいけないので、コントローラにdestroy
アクションも追加します。Sorceryにはlogout
というメソッドがあり、これを呼び出すことでユーザをログアウトさせることができます。ユーザがログアウトした後は、トップページにリダイレクトします。
def destroy logout redirect_to root_url, :notice => "Logged out!" end
次にroutesファイルを開いて、すべてをつなげるために、デフォルトの自動生成されたアクションを次のルートで置き換えます。
Auth::Application.routes.draw do get "logout" => "sessions#destroy", :as => "logout" get "login" => "sessions#new", :as => "login" get "signup" => "users#new", :as => "signup" resources :users resources :sessions get "secret" => "home#secret", :as => "secret" root :to => "home#index" end
認証に関連する機能を処理するいくつかの名前付きルート(named routes)やリソース(resources)があります。
新しく作成したこれらのページにユーザがアクセスできるように、新規にリンクが必要です。それらをレイアウトページに追加して、アプリケーションのすべてのページから見えるようにします。current_user
メソッドを使って、ユーザがログインがしているかどうかを確認できます。もしログインしていたら、メールアドレスをログアウトのリンクと一緒に表示します。もし現在のユーザがいない場合は、登録かログインをさせるためのページへのリンクを表示します。
<!DOCTYPE html> <html> <head> <title>Auth Example</title> <%= stylesheet_link_tag "application" %> <%= javascript_include_tag "application" %> <%= csrf_meta_tags %> </head> <body class="<%= params[:controller] %>"> <div id="container"> <div class="user_nav"> <% if current_user %> Logged in as <%= current_user.email %>. <%= link_to "Log out", logout_path %> <% else %> <%= link_to "Sign up", signup_path %> or <%= link_to "Log in", login_path %>. <% end %> </div> <% flash.each do |name, msg| %> <%= content_tag :div, msg, :id => "flash_#{name}" %> <% end %> <%= yield %> </div> </body> </html>
サイトをテストする準備ができました。トップページにアクセスすると、登録とログインのリンクが表示されています。
登録をクリックすると登録フォームが表示され、そのサイトに対してユーザ登録を行うことができます。その後、ログインをクリックして、登録した情報でログインすることができます。
権限を与える
サイトにログインしたので、秘密のページにアクセスすることができます。ログアウトしてから秘密のページにアクセスしてみると、まだページを見ることができます。そこで権限を付与する機能を追加して、ログインユーザだけがページにアクセスできるよう制限をかけます。
秘密の(secret)ページは、HomeController
内のアクションです。Sorceryが提供するrequire_login
というbefore_filter
を使えば、アクションへのアクセスを制限することが可能です。これを使ってsecretアクションへのアクセスを制限します。
class HomeController < ApplicationController before_filter :require_login, :only => :secret def index end def secret end end
このフィルターが起動されると、Sorceryはnot_authenticated
メソッドを呼び出します。ApplicationController
でこのメソッドをオーバーライドするのがいいでしょう。そうすることによって、権限が付与されなかったときの動作を制御することができます。ログインページにリダイレクトして警告メッセージを表示します。
class ApplicationController < ActionController::Base protect_from_forgery private def not_authenticated redirect_to login_url, :alert => "First log in to view ↵ this page." end end
ログアウトした状態で再度秘密のページにアクセスしてみると、ログインページにリダイレクトされてそこには警告メッセージが表示されています。
ログインすると秘密のページにリダイレクトされます。これはログインページにリダイレクトされたときに見ようとしていたページをSorceryが憶えているからです。
Sorceryに関する今回のエピソードは以上です。今回紹介した以外にも多くの機能があるので、ドキュメンテーションを参照してください。少し低いレベルで動作する認証ソリューションを探している場合は、Sorceryを検討してみる価値があるでしょう。