「Facebookアカウントでログイン」機能をつくる(Rails3, Devise, OmniAuth, Mongoid)
はじめに
巷のWebアプリでよく見かける「Facebookアカウントでログイン」「Twitterアカウントでログイン」のボタン。ユーザー認証の必要があるWebサービスでも、メールアドレスやパスワード設定の手間が省けてとっても便利だ。これはOAuthと言って、FacebookやTwitterといった外部サービス(認証プロバイダ)が提供するAPIを利用することで実現されている。
今回はRails3で上記の「Facebookアカウントでログイン」機能を実現する手順についてまとめてみた。
※なお、「マイグレーションが無くて楽」という理由でRailsのORMにはActiveRecordではなくMongoidを利用している。
使用するライブラリ
MongoDBのインストール
homebrewが入っていればMongoDBのインストールは簡単。
$ brew install mongodb
で一発。インストールが済んだら、とりあえず
$ mongod &
でバックグラウンド起動しておく。
本当はサービスとしてOS起動時に立ち上がるよう設定したいけど今はこれで 十分。
Devise, Mongoidインストール済みRailsアプリの作成
世の中には徳のある御仁がいて、yes/noの質問に答えるだけでDeviseとMongoidの設定済みRailsアプリを生成してくれるテンプレートがgithubで公開されている。今回はこれを利用させて頂く。
RailsApps/rails3-mongoid-devise · GitHub
テンプレートからRailsアプリを生成するには-mオプションでテンプレートファイルを指定する:
$ rails new my_auth_test_app -m https://github.com/RailsApps/rails3-application-templates/raw/master/rails3-mongoid-devise-template.rb -T -O
コマンドを実行するとウィザードがアプリで使用するテンプレートエンジンやテスティングフレームワークなどの項目を尋ねてくるので、以下のように返答した:
wizard You are using Rails version 3.2.6. wizard Checking configuration. Please confirm your preferences. insert config/application.rb recipe Running HAML recipe... haml Would you like to use Haml instead of ERB? (y/n) y y gemfile haml (>= 3.1.6) gemfile haml-rails (>= 0.3.4) recipe Running RSpec recipe... rspec Would you like to use RSpec instead of TestUnit? (y/n) y y question Which library would you like to use for test fixtures with RSpec? 1) None 2) factory_girl 3) machinist rspec Enter your selection: 2 2 gemfile rspec-rails (>= 2.11.0) gemfile database_cleaner (>= 0.8.0) gemfile mongoid-rspec (1.4.6) gemfile factory_girl_rails (>= 3.5.0) gemfile email_spec (>= 1.2.1) create features/support/email_spec.rb recipe Running Cucumber recipe... cucumber Would you like to use Cucumber for your BDD? (y/n) y y gemfile cucumber-rails (>= 1.3.0) gemfile capybara (>= 1.1.2) gemfile database_cleaner (>= 0.8.0) gemfile launchy (>= 2.1.0) recipe Running guard recipe... question Would you like to use Guard to automate your workflow? 1) No 2) Guard default configuration 3) Guard with LiveReload guard Enter your selection: 1 1 guard Guard recipe skipped. recipe Running Mongoid recipe... mongoid Would you like to use Mongoid to connect to a MongoDB database? (y/n) y y mongoid REMINDER: When creating a Rails app using Mongoid... mongoid you should add the '-O' flag to 'rails new' gemfile mongoid (>= 3.0.1) recipe Running ActionMailer recipe... question How will you send email? 1) SMTP account 2) Gmail account 3) SendGrid account 4) Mandrill by MailChimp account action_mailer Enter your selection: 1 1 recipe Running Devise recipe... question Would you like to use Devise for authentication? 1) No 2) Devise with default modules 3) Devise with Confirmable module 4) Devise with Confirmable and Invitable modules devise Enter your selection: 2 2 devise Would you like to manage authorization with CanCan & Rolify? (y/n) n n gemfile devise (>= 2.1.2) recipe Running AddUser recipe... recipe Running HomePage recipe... recipe Running HomePageUsers recipe... recipe Running SeedDatabase recipe... recipe Running UsersPage recipe... recipe Running html5 recipe... question Which front-end framework would you like for HTML5 and CSS? 1) None 2) Zurb Foundation 3) Twitter Bootstrap (less) 4) Twitter Bootstrap (sass) 5) Skeleton 6) Just normalize CSS for consistent styling html5 Enter your selection: 1 1 recipe Running SimpleForm recipe... question Which form gem would you like? 1) None 2) simple form 3) simple form (bootstrap) simple_form Enter your selection: 1 1 simple_form Form help recipe skipped. recipe Running Cleanup recipe... recipe Running Extras recipe... extras Would you like to use 'rails-footnotes' (it's SLOW!)? (y/n) n n extras Would you like to set a robots.txt file to ban spiders? (y/n) y y extras Would you like to add 'will_paginate' for pagination? (y/n) n n extras Add 'therubyracer' JavaScript runtime (for Linux users without node.js)? (y/n) n n extras Banning spiders by modifying 'public/robots.txt' recipe Running Git recipe... wizard Running 'bundle install'. This will take a while.
これでDeviceとMongoidのインストール済みアプリが出来上がった。おまけにfactory_girlもrspecもhamlも導入済み。超ラク。ところでCanCanとRollifyってよく知らないけど何だろう。
mongoidの設定
MongoDBへの接続設定ファイルをジェネレーターで作成する:
rails g mongoid:config
これでconfig/mongoid.yml というデフォルト状態の設定ファイルができあがる。コメントを抜かした中身はこんな感じ:
# config/mongoid.yml development: sessions: default: database: my_auth_test_app_development hosts: - localhost:27017 options: consistency: :strong options: test: sessions: default: database: my_auth_test_app_test hosts: - localhost:27017 options: consistency: :strong
サンプルアプリだし特に変更する必要も無いので今回はデフォルトのままにしておく。
mongodbの便利な所は、事前に上記データベースのcreateを実行しておく必要がないところ。初回のデータinsert時にデータベースが自動で作成される。
OmniAuthとDeviseの連携
DBの設定も完了してRailsアプリの土台は出来上がった。ここからOmniAuthとDeviseを連携させてFacebook経由でユーザー認証を行う仕組みを作っていく。連携と言ってもDeviseはver 1.2からOmniAuthによる認証をサポートしていて、OmniAuthの設定はすべてDevise側から行うことができる。
Facebook側の設定
Facebookを認証プロバイダーとして利用するためには、まずFacebook開発者サイトでアプリの登録を行っておく必要がある。
新しくアプリを作成するとアプリIDとシークレットキーが与えられるので、これをメモっとく(あとで必要)。
またアプリのサイトURLの設定も必要。今回はローカルのみで動かすので "http://localhost:3000/"とだけ設定しておいた。
Userモデルの作成
そもそもの認証の対象となるUserモデルのひな形を作成しておく。その前にdeviseの初期化も実行しておかないといけない。
$ rails g devise:install $ rails g devise User
ジェネレータで作成されたuser.rbを編集し、UserがOmniAuthの対象であることをDeviseに教えてやる。また認証情報を保持するため、Userモデルのひな形にはproviderとuidというフィールドを加えておく:
devise :omniauthable field :provider, :type => String # facebook等の認証プロバイダ field :uid, :type => String # 認証プロバイダ内のユーザーID
ここでrake routesを実行すると、ユーザーのomniauth用名前付きパスが作成されていることが分かる:
user_omniauth_authorize /users/auth/:provider(.:format) devise/omniauth_callbacks#passthru {:provider=>/facebook/} user_omniauth_callback /users/auth/:action/callback(.:format) devise/omniauth_callbacks#(?-mix:facebook)
サーバーを立ち上げhttp://localhost:3000/users/auth/facebookにアクセスすると、Facebookの認証用ページにリダイレクトされる:
このページでユーザーが「アプリへ移動」を選択すると、認証情報が/users/auth/facebook/callbackにコールバックされる、という寸法。あとはFBからのコールバックを受け取る処理を加えてあげればよい。
Facebookからのコールバックを処理する
config/routes.rbを編集し、omniauthのコールバック用コントローラーを下記の通り変更する:
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
コールバックの処理は次のように実装する:
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController def facebook @user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user) if @user.persisted? flash[:notice] = "Athentication successful!" sign_in(:user, @user) redirect_to root_path else session["devise.facebook_data"] = request.env["omniauth.auth"] redirect_to new_user_registration_url end end end
Facebookから送られる認証情報はrequest.env["omniauth.auth"]に渡されるため、これを使ってユーザーの検索/作成をしている。
Userモデルでは find_for_facebook_oauthを次のように実装する:
def self.find_for_facebook_oauth(auth, signed_in_resource=nil) user = User.where(:provider => auth.provider, :uid => auth.uid).first unless user user = User.create(name: auth.extra.raw_info.name, provider: auth.provider, uid: auth.uid, email: auth.info.email, password: Devise.friendly_token[0,20] ) end user end
ちなみにomniauthを使用する場合ユーザーがパスワードを入力する必要がないので、passwordフィールドにはDevise#friendly_tokenで生成したランダム文字列を渡している。
Viewを整える
整えるっていうか動作確認のためにリンク作るだけだけど...
Homeコントローラーを作成して、rootパスをhome#indexアクションに割り当て、index.html.hamlに次のように記述してやる。
%h1 Home#index %p Find me in app/views/home/index.html.haml - if !user_signed_in? = link_to "Facebookアカウントでログイン", user_omniauth_authorize_path(:facebook) - else = "#{current_user.name}としてログインしています." = link_to "ログアウト", destroy_user_session_path, :method => :delete
動作確認
FBの開発者サイトでテスト用のダミーユーザーを作成した。その名もミスBharambeman。彼女の疑似アカウントでFBにログインし、http://localhost:3000/にアクセスすると
「Facebookアカウントでログイン」のリンクを踏み、FBの認証用ページに移動する:
ここで「アプリへ移動」を選択してやれば、
Facebookのユーザー情報からユーザーが作成されたことが分かる。