LoginSignup
17
14

More than 5 years have passed since last update.

AuthlogicとOmniAuthを使って複数のアカウントと連携する

Last updated at Posted at 2014-10-22

OmniAuth を使った認証について、 devise と組み合わせてログインする記事は割と見かけるのだけれども Authlogic との組み合わせの記事はあまり見かけなくて、実際にやろうとした時にちょっとばたばたしたので自分用にメモ。

1ユーザに複数の認証アカウント

なんで devise でなくて authlogic 使いたかったかっていうと、1ユーザが複数のアカウントでログインできる状態にしたかったから。

そのためには

app/models/user.rb
class User < ActiveRecord::Model
  has_one :facebook_account
  has_one :twitter_account
end

というように、なっていてほしい。
でもって、ログイン処理の対象になるのは FacebookAccount や TwitterAccount でなくて User モデルであってほしい。

最初 devise でそういうことをやろうとしたんだけど、なんだかうまくいかなくて authlogic に乗り換えたわけです。

authlogic にも Facebook とか OpenID とかの Add on があるみたいなんですが、そちらはどんな実装になっているのかコードを読み切れなかったので、使っていません。
認証に関しては、対応サービスも多い OmniAuth 使うことにしました。

authlogic の準備

authlogic インストール

Gemfile
gem 'authlogic'
$ bundle

User

名前はもちろん、Userでなくてなんでも良い。

$ rails g model User persistence_token:string

persistence_token は authlogic が使うカラムなので、必ず作っておく必要がある。

app/models/user.rb
class User < ActiveRecord::Base
  acts_as_authentic
end

で、生成されたモデルの中で acts_as_authentic を呼ぶ。

UserSession

これも、名前はなんでも構わない。

app/models/user_session.rb
class UserSession < Authlogic::Session::Base
end

Authlogic::Session::Base を継承したクラスがログインセッションを扱うモデルになる。

ログインする時は UserSession.create だし、ログイン中のセッションを探す時は UserSession.find だし。まるで ActiveRecord::Base を継承したモデルのような操作感。
UserSession.new するとインスタンスが返ってきて、それを form_for に渡せるのも便利。

で、これがとても大事なことなんだけれども。
UserSession.newUserSession.create は、 UserSession.create(params[:user_session]) みたいな感じでユーザIDとパスワードを受け取ってログインするんだけれど。
それ以外にも、ログイン対象のモデル(今回なら User)のインスタンスを直接受け取ることもできる。
モデルのインスタンスを受け取ってログインできるの、超絶便利!

omniauth-facebook で認証する

omniauth-facebook

Gemfile
gem 'omniauth-facebook'

bundle する。

Facebook アプリの設定

config/initializers/omniauth_builder.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, ENV['FACEBOOK_APP_ID'], ENV['FACEBOOK_APP_SECRET']
end

FacebookAccount

$ rails g model FacebookAccount user:references uid:string access_token:string name:string image:text

migrate で useruid それぞれ unique index 指定しておくこと。

で、認証後にユーザを特定するためのメソッドを用意する。

app/models/facebook_account.rb
class FacebookAccount < ActiveRecord::Base
  belongs_to :user

  def self.authenticate(auth)
    account = self.where(uid: auth.uid).take
    account ||= self.new(uid: auth.uid)
    account.access_token = auth.credentials.token
    account.name = auth.info.name
    account.image = auth.info.image

    unless account.user
      account.user = User.create
    end

    account.save!
    account
  end
end

uid が見つかればそのアカウントを使うし、見つからなければ新しく作る。
ログイン中のユーザに対してアカウント追加したい場合はこれだとうまくいかないんだけど、今はまだ考えない。

受け取るのは OmniAuth::AuthHash インスタンス。
ついでなので、ユーザの情報を更新もしておく。

いよいよログイン

コールバックURL

Omniauth は、認証開始の URL は自動で作られてしまうのだけれども、コールバックは自分で設定する必要がある。

config/routes.rb
  %w(get post).each do |method|
    send(method, 'auth/:provider/callback' => 'user_sessions#create')
  end

ついでにログアウトも追加しておく

config/routes.rb
  get 'logout' => 'user_sessions#destroy'

UserSessionsController

app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
  def create
    account = account_model.authenticate(env['omniauth.auth'])
    @user_session = UserSession.new(account.user)
    if @user_session.save
      flash[:notice] = 'Authenticate success'
    else
      flash[:error] = 'Authenticate failed.'
    end
    redirect_to root_path
  end

  def destroy
    current_user_session.destroy
    redirect_to root_path
  end

  private
  def account_model
    # params[:provider] に意図していない文字列が入ってないかチェックしたりとか
    Object.const_get("#{params[:provider].capitalize}Account")
  end
end

ログイン中の情報

ログイン中のユーザ

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  helper_method :current_user_session, :current_user

  private

  def current_user_session
    return @current_user_session if defined?(@current_user_session)
    @current_user_session = UserSession.find
  end

  def current_user
    return @current_user if defined?(@current_user)
    @current_user = current_user_session && current_user_session.user
  end
end

これで current_user で、ログイン中ユーザが参照できる。

ログイン中かどうか

current_user を使っても良いんだけど。

app/controllers/application_controller.rb
  def login?
    !!current_user
  end

あると使うし、便利かなって。

ログイン必須

app/controllers/application_controller.rb
  def require_login
    unless login?
      raise # 何かちょうど良いエラーでも
    end
  end

後はログイン必須にしたいコントローラで。

class XxxController < ApplicationController.rb
  before_action :require_login
  # ...

ほかサービスのアカウントでも認証できるようにする

例えば、ここから Twitter アカウントでも認証させたくなったら。
FacebookAccount と同じ要領で追加すれば良いだけなので以下略。

あ、これじゃあタイトル詐欺だ、すみません。

Facebook.authenticate で、ユーザがログイン中の時のことを考慮しないといけないとか。
後、アカウント単位で連携を解除できるようにしたいとか。
そういうところは、ちょこっと気をつけないといけないと思うので、頑張ってください。

17
14
1

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
  3. You can use dark theme
What you can do with signing up
17
14