search
LoginSignup
233

More than 5 years have passed since last update.

posted at

updated at

DeviseとOmniauthで認証管理

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が提供する機能の必要なものだけ使うことができます。

ここでは何をやるか

Deviseを使って認証管理をします。
認証にはfacebookを使用します。

要件

  • ランディングページのようなページがある。
  • ログインしなければ見れないページがある。
  • facebookのOAuthを利用してログインする。

やってみる

1. ランディングページとなるページの作成

welcomeコントローラーをジェネレーターで作ります。

$ rails g controller Welcome index

2. / をwelcome#indexを設定

config/routes.rbget "welcome/indexを以下のように変更します。

config/routes.rb
  root "welcome#index"

3. ログイン後のページ /home を作成

ログイン後のページとなるhomeコントローラーを生成します。

$ rails g controller Home index

4. Devise, Omniauthを使うための記述

Gemfileに下記を追記してgemをインストールします。

Gemfile
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/environments/development.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 IDApp Secretを取得します。

config/initializers/devise.rb
require "omniauth-facebook"

Devise.setup do |config|

 

  config.omniauth( :facebook,
                   ENV['FACEBOOK_APP_ID'],
                   ENV['FACEBOOK_APP_SECRET'],
                   {:scope => 'email'} )
end

環境変数にApp IDApp Secretを入れてサーバを再起動します。
dotenvを使うと便利!)

$ export FACEBOOK_APP_ID="123456789012345"
$ export FACEBOOK_APP_SECRET="1234567890abcdefghijklmnopqrstuvwxyz"
$ rails s

9. UserモデルをOmniauthで認証可能にする

:omniauthableをUserモデルに追加します。

app/models/user.rb
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というヘルパーメソッド使ってログインリンクを生成します。

app/views/welcome/index.html.erb
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>

11. facebook認証後のコールバックメソッドを定義する

app/controllers/usersディレクトリを作成し、omniauth_callbacks_controller.rbを作ります。
request.env['omniauth.auth']というリクエストパラメータにコールバックされた値が入っています。
値を確認するために例外を発生させてその変数の中身を表示してみます。

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    raise request.env['omniauth.auth'].to_yaml
  end
end

12. facebookからのコールバックをログイン処理

まず、Userモデルにfind_for_facebook_oauthというクラスメソッドを追加します。
このメソッドはコールバックされた値のprovideruidからDBを検索します。
存在しなかったら新規ユーザーを生成して返却します。

app/models/user.rb
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


コールバックメソッドでは先ほど生成したメソッドにパラメータを渡してユーザーインスタンスを生成し、
ログイン処理をしてリダイレクトします。

app/controllers/users/omniauth_callbacks_controller.rb
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にルーティングを変更します。

config/routes.rb
  get "home", to: "home#index", as: "user_root"

14. ログイン・ログアウトのリンクを整理

下のコードではログイン中(user_signed_in?で確認できます)ならば「ユーザー名・ログアウトリンク」を、
そうでなければ「ログインリンク」を表示します。

app/views/layouts/application.html.erb
<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_filterauthenticate_user!を追加することによってログインしているかどうかを判別し、アクセス制御をします。

app/controllers/home_controller.rb
class HomeController < ApplicationController
  before_filter :authenticate_user!

  def index
  end
end

16. 15のリダイレクト先をfacebook認証のページに変更

authenticate_user!をオーバーライドして、facebook認証URLに書き換えます。

app/controllers/application_controller.rb
  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だったらkoalafb_graphを使ってAPIを叩くことも可能です。
使う際はscopeの指定をお忘れなく。

Githubに一連の流れを記しておきました。Commits · ytkt/TryDeviseAndOmniauth
(途中で気づいたんですが、ブランチを切らずに進めてしまったためとても見にくいです)

間違っているところ、マズイ所があれば教えていただけると幸いです。

Tips

Facebook permissions

config/initializers/devise.rbに設定するscopeの一覧です。
Permissions : Facebook開発者向けドキュメントの日本語訳とTips

dotenv

App Secretなどをハードコーディングしているのはよろしくないので、環境変数に入れてそれをRailsから読み込みます。
この際、dotenvを使うと便利です。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
233