#241 Simple OmniAuth
- Download:
- source codeProject Files in Zip (101 KB)
- mp4Full Size H.264 Video (13.5 MB)
- m4vSmaller H.264 Video (9.23 MB)
- webmFull Size VP8 Video (23.8 MB)
- ogvFull Size Theora Video (18.5 MB)
数週間前、エピソード235[動画を見る, 原文を読む]と236[動画を見る, 原文を読む]でOmniAuthについて説明しました。OmniAuthは、Twitterなどのサードパーティによる認証サービスを自分のRailsアプリケーションに統合することを可能にします。これらのエピソードで提示したシナリオでは、アプリケーションにすでにユーザ名とパスワードを管理するしくみ(この場合はDevice)が存在しているところにOmniAuthを統合するという、どちらかというと複雑なものでした。また、ユーザごとに複数の認証方法を提供していました。このような特別な要求がないのであれば、OmniAuthによる認証はずっと単純になるということをこのエピソードで紹介します。
今回扱うアプリケーションも、以前使用したシンプルなブログシステムです。現状では認証のしくみを持っていないので、OmniAuthだけを使ってTwitterによる認証機能を付加します。Authlogic、Deviseやその他の認証の手法を使わないので、OmniAuthがいかにシンプルかがわかるでしょう。
まず最初に、OmniAuth gemを追加します。Rails 3で作成するので、Gemfile
にgemを追記します。
gem "omniauth"
忘れずにbundler
を実行し、必要なgemがすべてインストールされていることを確認しましょう。次に設定ファイルomniauth.rb
を作成してOmniAuthをロードします。このファイル内に、OmniAuthをロードしTwitterを使用することを指定します。
Rails.application.config.middleware.use OmniAuth::Builder do provider :twitter, 'CONSUMER_KEY', 'CONSUMER_SECRET' end
アプリケーションにOmniAuthをミドルウェアとして付加することに注意してください。OmniAuthは、Rackミドルウェアにとても似ていて、アプリケーションの他の部分から切り離されているという点で非常に優れています。これによって、アプリケーションに自由な形で認証機能を実装することが可能です。
今回、認証情報の提供元はTwitterのみとします。ここでCONSUMER_KEY
とCONSUMER_SECRET
の値を実際の値に置き換えます。これらの値は、Twitterの開発者向けサイトでユーザ情報を入力しアプリケーションを登録したら入手できます。登録が終わったら、認証機能の追加作業を始めましょう。
レイアウトファイル内のプレースホルダーのテキストをログインリンクと置き換えます。これは、OmniAuthのtwitter URLにリンクされます。今回は/auth/twitter
になります。
<div id="user_nav"> <%= link_to "Sign in with Twitter", "/auth/twitter" %> </div>
これでログイン用のリンクができました。クリックするとTwitterのサイトにリダイレクトされ、(作成中の)アプリケーションへのアクセスを許可するかどうか聞かれます。「許可する(allow)」をクリックすると、作成中のアプリケーションにリダイレクトされルーティングエラーが表示されます。
このエラーが表示される理由は、ブラウザのリダイレクト先がOmniAuthの認証が成功したときに起動されるコールバックURLになっているからです。これに合わせて、routesファイル内のルートを設定します。認証処理のためにSessionsController
を作成して、その中のcreate
アクションにリダイレクトさせます。
Blog::Application.routes.draw do |map| root :to => "articles#index" match "/auth/:provider/callback" => "sessions#create" resources :comments resources :articles end
URLのtwitter
の部分を、:provider
という引数に置き換えました。これは、Twitter以外の認証元を利用した場合にもルートが対応できるようにするためです。
次にSessionsコントローラを新規に作成します。
$ rails g controller sessions
認証後にアプリケーションに戻ると、認証に関する情報をハッシュ形式で持つomniauth.auth
という環境変数が利用できます。ここでは一時的に、例外を発生させてこの情報を表示させます。
class SessionsController < ApplicationController def create raise request.env["omniauth.auth"].to_yaml end end
ログインすると、アプリケーションにリダイレクトしたときにTwitterの認証情報が表示されます。
このハッシュ内の情報は、既存のユーザとしてログインするか、あるいは一致するアカウントがない場合に新規ユーザを作成するときに利用することができます。アプリケーションにUser
モデルがないので作成しましょう。
$ rails g model user provider:string uid:string name:string
このモデルにはprovider
とuid
という属性があり、ユーザがログインしたときに、これらを使ってユーザを特定します。さらにname
フィールドを追加します。次にデータベースをmigrateします。
$ rake db:migrate
ではここでSessionsController
に戻って、OmniAuthの情報を使って既存ユーザを呼び出すか新規ユーザを作成します。
class SessionsController < ApplicationController def create auth = request.env["omniauth.auth"] user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth) end end
create
メソッド内で、OmniAuthハッシュ内のprovider
とuid
の値からユーザの検索を試みます。もし何も一致しなかったら、User
内の、今から作るcreate_with_omniauth
というクラスメソッドを使って新規ユーザを作成します。
class User < ActiveRecord::Base def self.create_with_omniauth(auth) create!do |user| user.provider = auth["provider"] user.uid = auth["uid"] user.name = auth["user_info"]["name"] end end end
上のメソッドではcreate!
をブロックと一緒に呼び出します。これによって新規ユーザの情報がデータベースに保存される前に修正することができるようになります。このメソッドを書いたら、コントローラに戻ってcreate
メソッドを完成させます。セッションにユーザid
を保存し、スタートページにリダイレクトしてflashメッセージを表示します。
class SessionsController < ApplicationController def create auth = request.env["omniauth.auth"] user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth) session[:user_id] = user.id redirect_to root_url, :notice => "Signed in!" end end
ここで「Twitterでログイン(Sign in with Twitter)」のリンクをクリックすると、(Twitterアカウントにすでにログインしていると判断して)自動的にログインします。
このページには問題があります。ログインしたのですが、ページの最上部にはまだ「ログイン(sign in)」の表示が残っています。ここは代わりに、ユーザ名とログアウトのためのリンクが表示されているべきでしょう。
現在ログイン中のユーザの情報にアクセスする必要があるので、ApplicationController
クラスにcurrent_user
メソッドを追加します。このメソッドは、セッションのuser_id
からログインユーザの情報を得て、結果をインスタンス変数に保持します。この情報に、コントローラからだけでなくビューからもアクセスできるよう、ヘルパーメソッドとして作成します。
class ApplicationController < ActionController::Base protect_from_forgery helper_method :current_user private def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end end
次にレイアウトファイルを修正し、ユーザがログインしていないときだけログインのリンクが表示されるようにします。ログイン後は代わりにユーザ名を表示します。
<div id="user_nav"> <% if current_user %> Welcome, <%= current_user.name %>! <% else %> <%= link_to "Sign in with Twitter", "/auth/twitter" %> <% end %> </div>
ページをリロードすると、ページ上部に現在ログインしているユーザ名で「ようこそ、Eifion!」と表示されています。
ログアウト用のリンクもあれば便利なので、ユーザ名の隣に追加します。これは、まだ作っていないsignout_path
へのリンクになります。
<div id="user_nav"> <% if current_user %> Welcome, <%= current_user.name %>! <%= link_to "Sign Out", signout_path %> <% else %> <%= link_to "Sign in with Twitter", "/auth/twitter" %> <% end %> </div>
signout_path
をSessionController
のdestroy
アクションに対応づけます。
match "/signout" => "sessions#destroy", :as => :signout
ルートに:as => :signout
を追加することで、上のsignout_path
を参照できるようになります。destroy
アクションを書きます。
def destroy session[:user_id] = nil redirect_to root_url, :notice => "Signed out!" end
ここでページをリロードすると、ログアウト用のリンクが表示され、クリックするとログアウトします。
外部認証を追加する
OmniAuthを使っていれば、外部認証を追加するのも簡単です。例えば、Facebookで認証させたければ、ページレイアウトにもう一つリンクを追加するだけです。
<div id="user_nav"> <% if current_user %> Welcome, <%= current_user.name %>! <%= link_to "Sign Out", signout_path %> <% else %> <%= link_to "Sign in with Twitter", "/auth/twitter" %> <%= link_to "Sign in with Facebook", "/auth/facebook" %> <% end %> </div>
正しい証明情報を設定ファイルomniauth.rb
に追加すれば、アプリケーションにFacebookを介してログインできるようになります。OpenId、LinkedIn、その他の認証プロバイダを追加することができます。
もしアプケーションに従来のユーザ名やパスワードによる認証が必要なく、アカウントごとに異なった種類の認証をしなくてもいいのであれば、今回の方法は効率がよく、どのようなアプリケーションにも簡単に付加することができます。