とあるRails 3を使っているたいやき用のCMSでDeviseを使ってOpenID認証をするようにしたので、そのやり方を紹介します。RubyはRuby 1.9.2 RC2も出ていますが、今回はRuby 1.9.1を使います。
Deviseとは
DeviseはRackベースの認証システムです。バックエンドにWardenを利用しているため、Basic認証やOpenID、OAuthなど認証方法を切り替えることができます。
ただ、以下の説明を読んでみてもらってもわかる通り、動き出すまでにそこそこの作業が必要になります。機能は豊富なので、動き出したらカスタマイズしてアプリケーションの要求に合わせていくことができるでしょう。日本語での情報もあまりありませんが、探せばいくつかはあるので、試してみてはいかがでしょうか。
とはいえ、今回はDeviseのデフォルトの認証方法ではなく、OpenIDでのみ認証することにします。また、未登録のユーザがログインしようとしたときは自動的に新規ユーザを作成することにします。このようにも使えるという例ということで読むとよいかもしれません。
インストール
まず、Rails 3.0 beta4をインストールします。
% sudo gem1.9.1 install rails --pre
サンプル用のアプリケーションを作ります。
% ruby1.9.1 rails new taiyaki
% cd taiyaki
次にDeviseをインストールします。
% sudo gem1.9.1 install devise --version=1.1.rc2
インストールしたDeviseを利用するため、以下のようにGemfileに追記します。
Gemfile:
gem 'devise', "1.1.rc2"
アプリケーションにDeviseが動作するために必要なファイルをインストールします。config/initializers/devise.rbなど主に設定ファイルです。
% ruby1.9.1 script/rails generate devise:install
いくつかは手動で設定する必要があります。それぞれ以下の通りです。
Deviseはパスワードの再設定をする機能もあり、そのときはユーザにメールを送信します。そのような機能を使うときはActionMailerのURL生成オプションを設定する必要があります。例えば、開発時のホスト情報を設定する場合は以下のようになります。
config/environments/development.rb:
config.action_mailer.default_url_options = {:host => 'localhost:3000'}
Deviseはリダイレクト先のURLを生成するときなどにデフォルトではroot_path
を使うので、rootパスへのマッピングを追加します。以下の例ではwelcome#index
を指定しているので、後でWelcomeControllerを作ります。
config/routes.rb:
root :to => "welcome#index"
Deviseはnoticeとalertのflashを設定するので、レイアウトに追加しておくとよいでしょう。例えば、以下のようにyield
の前に追加します。
app/views/layouts/application.html.erb:
<%# ... %>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= yield %>
<%# ... %>
作成
これでインストールは完了したので、コントローラーやモデルを作成します。
まず、ユーザー用のモデルを作成します。
% ruby1.9.1 script/rails generate devise User
このとき生成されるスキーマは、以下のようにデータベース上にパスワードのダイジェストなどの情報を持ち、それを利用して認証することになります。
db/migrate/XXXX_devise_create_users.rb:
class DeviseCreateUsers < ActiveRecord::Migration
def self.up
create_table(:users) do |t|
t.database_authenticatable :null => false
t.recoverable
t.rememberable
t.trackable
# t.confirmable
# t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
# t.token_authenticatable
t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
# add_index :users, :confirmation_token, :unique => true
# add_index :users, :unlock_token, :unique => true
end
def self.down
drop_table :users
end
end
しかし、今回は自分では認証情報を持たずにOpenIDで認証するので、データベース上に認証情報を持たないようにします。代わりにOpenID用のカラムを追加します。
db/migrate/XXXX_devise_create_users.rb(変更後):
class DeviseCreateUsers < ActiveRecord::Migration
def self.up
create_table(:users) do |t|
t.string :email
t.string :nickname
t.string :identity_url
t.string :fullname
t.string :birth_date
t.integer :gender
t.string :postcode
t.string :country
t.string :language
t.string :timezone
t.rememberable
t.trackable
t.confirmable
t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
t.token_authenticatable
t.timestamps
end
add_index :users, :identity_url, :unique => true
add_index :users, :email, :unique => true
add_index :users, :confirmation_token, :unique => true
add_index :users, :unlock_token, :unique => true
end
def self.down
drop_table :users
end
end
変更したらスキーマを反映させます。
% rake1.9.1 db:migrate
モデルのコードにもデータベースで認証するためのコードが入っています。今回は必要のないユーザ登録用の:registerable
オプションやパスワードの入力チェックなどをする:validatable
オプションなどは外します。password_required?
メソッドをオーバーライドしているのは、OpenIDで認証するためパスワードが必要がないからです。
app/models/user.rb:
class User < ActiveRecord::Base
...
# devise :database_authenticatable, :registerable,
# :recoverable, :rememberable, :trackable, :validatable
devise :database_authenticatable, :rememberable, :trackable
...
def password_required?
false
end
end
Deviseではログイン画面などデフォルトのビューも提供してくれますが、今回はOpenIDを使った認証にするためビューをカスタマイズします。ビューをカスタマイズする場合は、コントローラーごとカスタマイズする方法と、ビューだけカスタマイズする方法がありますが、今回はコントローラーごとカスタマイズする方法にします。
コントローラーをカスタマイズするにはconfig/routes.rbに追加されたdevise_for
で:controllers
オプションを指定します。以下のように指定するとUsers::SessionsController
コントローラーを使います。
config/routes.rb:
devise_for(:users,
:controllers => {:sessions => "users/sessions"})
コントローラーを作成します。
% ruby1.9.1 script/rails generate controller Users::Sessions
コントローラーをカスタマイズする場合は、ApplicationController
ではなくDevise::SessionsController
を継承します。
app/controllers/users/sessions_controller.rb:
class Users::SessionsController < Devise::SessionsController
end
ログインフォームではOpenID用の識別子を入力してもらうようにします。
app/views/users/sessions/new.html.erb:
<%= form_for(resource,
:as => resource_name,
:url => session_path(resource_name)) do |f| %>
<p>
<label for="openid_identifier" >OpenID URL:</label>
<%= text_field_tag :openid_identifier %>
</p>
<p><%= f.label :remember_me %> <%= f.check_box :remember_me %></p>
<p><%= f.submit "Login" %></p>
<% end %>
あとは、トップページを準備すれば画面を確認することができます。
トップページ用のコントローラーを生成します。
% ruby1.9.1 script/rails generate controller welcome index
% rm public/index.html
トップページではログインページに移動できるようにします。ログイン時はログイン中のユーザ情報を表示します。
app/views/welcome/index.html.erb:
<h1>Welcome#index</h1>
<% if user_signed_in? %>
<p>ようこそ<%= current_user.nickname %>さん</p>
<%= link_to("ログアウト", destroy_user_session_path) %>
<% else %>
<%= link_to("ログイン", new_user_session_path) %>
<% end %>
サーバを起動します。
% ruby1.9.1 script/rails server
http://localhost:3000/にアクセスすると以下のような画面になります。
ログインページに行くと以下のようなフォームになります。
OpenID対応
それではOpenIDに対応します。DeviseからOpenIDを使うために、warden-openidを使います。
% sudo gem1.9.1 install warden-openid
Gemfileにも追記します。
Gemfile:
gem 'warden-openid'
WargenでOpenIDを使うようにします。
config/initializers/devise.rb:
Devise.setup do |config|
...
config.warden do |manager|
manager.default_strategies(:openid, :scope => :user)
end
end
OpenIDの設定をします。warden-openidではOpenIDの認証が成功した時にコールバックが実行され、そこで認証情報に対応したアプリケーション用のユーザを返すことになります。今回は、ここで、ユーザが存在しない場合は自動的に新規ユーザを作成することにします。
config/initializers/openid.rb:
Rails.application.config.middleware.insert(Warden::Manager, Rack::OpenID)
Warden::OpenID.configure do |config|
config.required_fields = User.required_open_id_fields
config.optional_fields = User.optional_open_id_fields
config.user_finder do |response|
user = User.find_by_identity_url(response.identity_url)
if user.nil?
user = User.new
user.extract_open_id_values(response)
unless user.save
message = "failed to create user: "
message << "#{users.errors.full_messages.inspect}: "
message << user.inspect
Rails.logger.error(message)
user = nil
end
end
user
end
end
OpenIDの情報とアプリケーションのユーザ情報をマッピングする処理はモデルで行います。
app/models/user.rb:
class User < ActiveRecord::Base
REQUIRED_FIELDS = {
:nickname => "nickname",
}
OPTIONAL_FIELDS = {
:email => "email",
:fullname => "fullname",
:birth_date => "dob",
:gender => "gender",
:postcode => "postcode",
:country => "country",
:language => "language",
:timezone => "timezone"
}
class << self
def required_open_id_fields
REQUIRED_FIELDS.values
end
def optional_open_id_fields
OPTIONAL_FIELDS.values
end
end
def password_required?
false
end
def extract_open_id_values(response)
profile_data = {}
[OpenID::SReg::Response, OpenID::AX::FetchResponse].each do |response_class|
data_response = response_class.from_success_response(response)
profile_data.merge!(data_response.data) if data_response
end
[REQUIRED_FIELDS, OPTIONAL_FIELDS].each do |fields|
fields.each do |model_key, profile_key|
unless profile_data[profile_key].blank?
self.send("#{model_key}=", profile_data[profile_key])
end
end
end
self.identity_url = response.identity_url
self.nickname ||= identity_url
end
end
一応、ニックネーム情報は欲しいとリクエストしますが、もらえなくてもなんとなく動くようになっています。この状態でログインページにOpenID識別子を入力して、認証に成功するとアプリケーションにログインする事ができます。
ただし、現在リリースされているruby-openidはRuby 1.9のEncodingに対応していないため、認証中にASCII以外の文字列を含むページにアクセスすることになると失敗します。これを修正する方法はRuby 1.9.1 supportで報告済みですが、まだ取り込まれていません。
まとめ
Rails 3.0 beta4でDeviseを使ってOpenID認証する方法を紹介しました。Rails 3で認証まわりはどうしようか、と考えていている人は試してみるとよいかもしれません。ただ、betaやrcのものを使っているので、これから使い方は変わっていく可能性が高いと考えられます。注意してください。