Ruby on Rails Advent Calendar 21日目の記事です。
DeviseとOmniauthを使って認証管理をしてみます。
Deviseとは何か
Devise is a flexible authentication solution for Rails based on Warden. It:
- Is Rack based;
- Is a complete MVC solution based on Rails engines;
- Allows you to have multiple models signed in at the same time;
- Is based on a modularity concept: use just what you really need.
とGithubにあるように、
WardenをベースとしたRails向けフレキシブル
オーセンティケーション
ソリューション
です。
- Rackベース
- とはいってもRails専用。
- Rails上で完全なMVCソリューションベース
- 完全なMVCらしいです。
- 同時に複数のモデルでのサインインが可能
- 例えばUserモデルとAdminモデルを作った時、同時に2つのロールでログインできます。
- モジュール方式のコンセプトであり、必要な物だけを使うことができる
- 10個のモジュールを
devise
というDSLで付けたり外したりして、Devise
が提供する機能の必要なものだけ使うことができます。
- 10個のモジュールを
ここでは何をやるか
Deviseを使って認証管理をします。
認証にはfacebookを使用します。
要件
- ランディングページのようなページがある。
- ログインしなければ見れないページがある。
- facebookのOAuthを利用してログインする。
やってみる
1. ランディングページとなるページの作成
welcome
コントローラーをジェネレーターで作ります。
$ rails g controller Welcome index
2. / をwelcome#indexを設定
config/routes.rb
のget "welcome/index
を以下のように変更します。
root "welcome#index"
3. ログイン後のページ /home を作成
ログイン後のページとなるhome
コントローラーを生成します。
$ rails g controller Home index
4. Devise, Omniauthを使うための記述
Gemfile
に下記を追記してgemをインストールします。
gem 'devise'
gem 'omniauth'
gem 'omniauth-facebook'
$ bundle install
5. Deviseを使うための準備
作っているアプリケーションがdeviseを使えるようにします。
$ rails g devise:install
create config/initializers/devise.rb
create config/locales/devise.en.yml
===============================================================================
Some setup you must do manually if you haven't yet:
1. Ensure you have defined default url options in your environments files. Here
is an example of default_url_options appropriate for a development environment
in config/environments/development.rb:
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
In production, :host should be set to the actual host of your application.
2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:
root :to => "home#index"
3. Ensure you have flash messages in app/views/layouts/application.html.erb.
For example:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
4. If you are deploying on Heroku with Rails 3.2 only, you may want to set:
config.assets.initialize_on_precompile = false
On config/application.rb forcing your application to not access the DB
or load models when precompiling your assets.
5. You can copy Devise views (for customization) to your app by running:
rails g devise:views
===============================================================================
いろいろ表示されているので、実行していきます。
6. ActionMailerのデフォルトURLオプションを変更
production環境では別途config/environments/production.rb
に設定が必要です。
config.action_mailer.default_url_options = { :host => 'localhost:3000' }
7. Userモデルを生成
deviseのジェネレーターを使ってUserモデルを作ります。
自動生成する他に以下のカラムを追加します。
- 複数のサービスログインを可能にするための
provider
- facebookなどでのユーザーidである
uid
- ログイン中に表示させるための名前
name
- サービスへのアクセストークン
token
$ rails g devise User provider:string uid:string name:string token:string
8. FacebookのApp IDとApp Secretをセット
https://developers.facebook.com/apps/ へアクセスしてApp ID
とApp Secret
を取得します。
require "omniauth-facebook"
Devise.setup do |config|
…
config.omniauth( :facebook,
ENV['FACEBOOK_APP_ID'],
ENV['FACEBOOK_APP_SECRET'],
{:scope => 'email'} )
end
環境変数にApp ID
とApp Secret
を入れてサーバを再起動します。
(dotenvを使うと便利!)
$ export FACEBOOK_APP_ID="123456789012345"
$ export FACEBOOK_APP_SECRET="1234567890abcdefghijklmnopqrstuvwxyz"
$ rails s
9. UserモデルをOmniauthで認証可能にする
:omniauthable
をUserモデルに追加します。
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
end
10. Facebookログインリンクを設置
user_omniauth_authorize_path
というヘルパーメソッド使ってログインリンクを生成します。
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>
11. facebook認証後のコールバックメソッドを定義する
app/controllers/users
ディレクトリを作成し、omniauth_callbacks_controller.rb
を作ります。
request.env['omniauth.auth']
というリクエストパラメータにコールバックされた値が入っています。
値を確認するために例外を発生させてその変数の中身を表示してみます。
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
raise request.env['omniauth.auth'].to_yaml
end
end
12. facebookからのコールバックをログイン処理
まず、Userモデルにfind_for_facebook_oauth
というクラスメソッドを追加します。
このメソッドはコールバックされた値のprovider
とuid
からDBを検索します。
存在しなかったら新規ユーザーを生成して返却します。
def self.find_for_facebook_oauth(auth)
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,
token: auth.credentials.token,
password: Devise.friendly_token[0,20] )
end
return user
end
コールバックメソッドでは先ほど生成したメソッドにパラメータを渡してユーザーインスタンスを生成し、
ログイン処理をしてリダイレクトします。
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def facebook
@user = User.find_for_facebook_oauth(request.env["omniauth.auth"])
if @user.persisted?
sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
13. ログイン後のリダイレクト先を変更
user_root
という別名をつけるとそこへリダイレクトしてくれます。
同時に/home/index
から/home
にルーティングを変更します。
get "home", to: "home#index", as: "user_root"
14. ログイン・ログアウトのリンクを整理
下のコードではログイン中(user_signed_in?
で確認できます)ならば「ユーザー名・ログアウトリンク」を、
そうでなければ「ログインリンク」を表示します。
<div id="user-info">
<% if user_signed_in? %>
<%= current_user.name %>
[<%= link_to 'Sign out', destroy_user_session_path, method: :delete %>]
<% else %>
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>
<% end %>
</div>
15. homeコントローラーへのアクセスをログイン中のみに制限
before_filter
にauthenticate_user!
を追加することによってログインしているかどうかを判別し、アクセス制御をします。
class HomeController < ApplicationController
before_filter :authenticate_user!
def index
end
end
16. 15のリダイレクト先をfacebook認証のページに変更
authenticate_user!
をオーバーライドして、facebook認証URLに書き換えます。
def authenticate_user!
session[:user_return_to] = env['PATH_INFO']
redirect_to user_omniauth_authorize_path(:facebook) unless user_signed_in?
end
さいごに
DeviseとOmniauthを使えばログイン管理がとても簡単に実装することができます。(ちょっと長いですが)
今回はfacebookを対象としましたが、サービスへのApp登録、コールバックメソッド定義をすればTwitterでもGoogleでもログイン認証の選択肢を増やすことができます。
Porvider一覧はOmniauthのWikiで確認できます。List of Strategies · intridea/omniauth Wiki
omniauth用のscaffoldを作るGemを作っている方もshu0115/minimum-omniauth-scaffold
今回はアクセストークンを保存しているので、Facebookだったらkoalaやfb_graphを使ってAPIを叩くことも可能です。
使う際はscope
の指定をお忘れなく。
Githubに一連の流れを記しておきました。Commits · ytkt/TryDeviseAndOmniauth
(途中で気づいたんですが、ブランチを切らずに進めてしまったためとても見にくいです)
間違っているところ、マズイ所があれば教えていただけると幸いです。
Tips
Facebook permissions
config/initializers/devise.rb
に設定するscopeの一覧です。
[Permissions : Facebook開発者向けドキュメントの日本語訳とTips]
(http://facebook-docs.oklahome.net/archives/51927681.html)
dotenv
App Secret
などをハードコーディングしているのはよろしくないので、環境変数に入れてそれをRailsから読み込みます。
この際、dotenvを使うと便利です。