Rails4 + Devise + OmniAuthで複数のログイン機構を作る
やりたいこと
- adminとuserでmodelを分けて、別々のログイン機構を持ちたい。
- adminは通常のdeviseで、emailとpasswordを使用したログイン。
- userはdevise+Omniauthで、Twitter, Facebook, Githubのアカウントを利用してログイン。
初回アクセス時、各サービスでOAuthの認証を受けてcallbackが呼ばれた際に渡されたauth情報を元に、別途emailやpasswordを持たずにユーザー登録。 まだ検証中なので実装のための下調べをおおまかにメモった感じなので実装の場合は気をつける。
材料
・Rails(4)
・Devise
・Omniauth
・Omniauth-twitter
・Omniauth-facebook
Deviseのインストールと各種部品の作成
devise:installをして、管理対象となるユーザーのVMCを作成
rails g devise:install
rails g devise CustomerUser
rails g devise:controllers customer
rails g devise:views customer
rails g devise AdminUser
rails g devise:controllers admin
rails g devise:views admin
routesでパスを分ける
routesでadmin/sign_inとcustomer/sign_inといった感じに処理を分ける
Rails.application.routes.draw do
namespace :admin do
devise_for :admin_users, path: '',
:path_names => {:sign_in => 'login', :sign_out => 'logout', :sign_up => 'register'},
:controllers => {
:sessions => "admin/sessions",
:registrations => "admin/registrations",
:passwords => "admin/passwords"
}
end
namespace :customer do
devise_for :customers, :path => '',
:path_names => {:sign_in => 'login', :sign_out => 'logout', :sign_up => 'register'},
:controllers => {
:sessions => "customer/sessions",
:registrations => "customer/registrations",
:passwords => "customer/passwords",
:omniauth_callbacks => "customer/omniauth_callbacks"
}
end
root 'top#index'
end
ここでscope長い問題
namespace :adminの以下に:admin_usersってやった余波でscopeが冗長に。
user_signed_in?も、「admin_admin_user_signed_in?」みたいになっちゃって長い。
設計ミスだなーどうしようかなーと思いつつ検証なのでそのまま進める。
本当はnamespace以下じゃなくてdevise_forのpathにadminと記述して
他のadmin系処理だけnamespace以下に記述すればいいんだねって気づいた時にはすでにおすしなのであった。
deviseのconfigを設定する
deviseのconfig設定で、scoped_viewsをtrueに設定し
omniauth用のAPP IDなどを設定しておく。KEYの類は環境変数に。
local開発時はdotenv-railsというgemで環境変数を管理すると便利ねぇ。
config/initializers/devise.rb
config.scoped_views = true
config.omniauth :twitter, ENV['TW_API_KEY'], ENV['TW_SECRET_KEY']
config.omniauth :facebook, ENV['FB_APP_ID'], ENV['FB_SECRET_KEY']
modelの設定
FBだけfirst_or_createの書き方だとうまくusernameが保存できなかった。
CustomerUser.rb
class CustomerUser < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:validatable, :omniauthable, :omniauth_providers => [:twitter, :facebook]
validates :uid,
uniqueness: {
message: "このアカウントは既に登録済みです",
scope: [:provider]
}
def self.find_for_twitter_oauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create! do |user|
user.username = auth.info.nickname
end
end
def self.find_for_facebook_oauth(auth)
user = CustomerUser.where(:provider => auth.provider, :uid => auth.uid).first
unless user
user = CustomerUser.create(
username: auth.extra.raw_info.name,
provider: auth.provider,
#token: auth.credentials.token,
uid: auth.uid
)
end
user
end
def self.new_with_session(params, session)
if session["devise.user_attributes"]
new(session["devise.user_attributes"], without_protection: true) do |user|
user.attributes = params
user.valid?
end
else
super
end
end
# providerが入っている場合
def password_required?
super && provider.blank?
end
def email_required?
false
end
def update_with_password(params, *options)
if encrypted_password.blank?
update_attributes(params, *options)
else
super
end
end
end
omniauth_callbacks_controllerを作る
devise:controllersで入れ物は作ってくれているので、中に処理を追加する。
それぞれのSNS別に処理を作っても、共通で作ってもよい。
omniauth_callbacks_controller.rb
def twitter
@user = CustomerUser.find_for_twitter_oauth(request.env["omniauth.auth"])
if @user.persisted?
flash.notice = "ログインしました"
else
flash.notice = "ログインできませんでした"
end
sign_in_and_redirect @user, :event => :authentication
end
Viewにログインのリンクを設置
<a href="<%= customer_user_omniauth_authorize_path(:twitter) %>">Twitter Login</a>
<a href="<%= customer_user_omniauth_authorize_path(:facebook) %>">Facebook Login</a>
せっかくなのでPNotifyでログインしたよメッセージを出す
https://github.com/sciactive/pnotify/wiki/Rails-flash-integration-with-PNotify
Gemfileに追加してbundle installしておく
gem 'rails-assets-pnotify'
gem 'unobtrusive_flash', '>=3'
application_controllerにfilterを追加する
application_controller.rb
after_filter :prepare_unobtrusive_flash
CSSを呼ぶ
application.css
*= require pnotify
flash.jsを作る
このへんは通常のPNotifyと同じなのでPNotifyの説明を見ながらいい感じにする。
flash.js
$(document).ready(function() {
$(window).bind('rails:flash', function(e, params) {
new PNotify({
title: params.type,
text: params.message,
type: params.type,
icon: false
});
});
});
JSも呼ぶ
application.js
//= require pnotify
//= require unobtrusive_flash
//= require flash