markdown [rails:devise] Ruby on Rails的身份验证gem。 #ruby #rails

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown [rails:devise] Ruby on Rails的身份验证gem。 #ruby #rails相关的知识,希望对你有一定的参考价值。

## OVERVIEW ##
> [plataformatec/devise - github.com](https://github.com/plataformatec/devise)  
> [devise - ruby.doc.info](https://www.rubydoc.info/github/plataformatec/devise/)

[warden](https://github.com/wardencommunity/warden) をベースにした Ruby on Rails のための認証管理 gem で、Rails 開発では広く一般的に利用されている。認証まわりの「よくあるやつ」を包括的に提供してくれる。

### Modules
> [[*Rails*] deviseの使い方(rails5版)](https://qiita.com/cigalecigales/items/f4274088f20832252374)

- database_authenticatable : パスワードのハッシュ登録・検証機能の提供、認証方式に POST か BASIC 認証がある
- registerable : 登録 ( サインアップ ) やユーザ自身の編集・削除を提供
- recoverable : パスワードリセット・通知の提供
- rememberable : Cookie を保存しユーザを記憶するためのトークン生成・削除を提供
- trackable : サインイン回数・時間・IP アドレスの記録を提供
- validatable : メールアドレスやパスワードのバリデーションを提供
- confirmable : 登録 → ワンタイム URL つきメール送信 → 本登録というフローや、アカウントの本登録済み検証を提供
- lockable : ログイン失敗 → ロック → メールや時間経過でロック解除といったアカウントロック機能を提供
- timeoutable : 一定時間活動していないアカウントのセッション破棄を提供
- omniauthable : omniauth との連携機能を提供

### Refs
- [定番のgem「devise」活用法 (1)](https://codezine.jp/article/detail/10813?mode=print)
- [定番のgem「devise」活用法 (2)](https://codezine.jp/article/detail/10899?mode=print)
- [devise-i18n](https://github.com/tigrish/devise-i18n)

### Install to rails
```sh
# gem のインストール
$ vi Gemfile
> gem 'devise'
$ bundle install

# devise 設定ファイルの作成
$ rails g devise:install
> create  config/initializers/devise.rb  # devise 設定ファイル
> create  config/locales/devise.en.yml   # devise 用のロケールファイル

# 利用ロケールを ( スタブでも良いので ) 用意
# @see https://qiita.com/MasatoYoshioka@github/items/8d910e793e7c485403bb
$ cp config/devise.en.yml config/devise.ja.yml
$ vi config/devise.ja.yml  # 行頭 en を ja へ

# devise 設定ファイルの調整
$ vi config/initializers/devise.rb
> config.scoped_views = true          # 名前空間分けに対応させる
> mailer_sender                       # パスワードを忘れた場合などの際に送られるメールのfromアドレスを指定
> require 'devise/orm/active_record'  # deviseがサポートする ORM ( デフォルト: ActiveRecord )
> case_insensitive_keys [:email]      # 大文字/小文字を区別しないカラム名を指定
> strip_whitespace_keys [:email]      # 空白を除去するカラム名を指定
> password_length       6..128        # Validatable モジュール用の設定 ( パスワードの長さ指定 )
> reset_password_within 6.hours       # パスワードリセットするための URL の有効期限を指定
> config.timeout_in = 30.minutes      # タイムアウト時間を設定
> config.sign_out_via = :delete       # ログアウトのメソッドを指定、デフォルト :delete なので :get にしたほうが無難
> if Rails.env.production?            # 本番環境では Rails の secret_key_base を secret_key として利用
>   config.secret_key = Rails.application.credentials.secret_key_base
> end

# Action Mailer の URL 設定をしておく
$ vi config/application.rb
> config.action_mailer.default_url_options = { host: ENV.fetch('DOMAIN') }

# views/layouts で Flash が出るようにしておく ( 以下コードは適当 )
$ vi app/views/layouts/application.html.erb
> <% if !notice.nil? %><p class="notice"><%= notice %></p><% end %>
> <% if !alert.nil? %><p class="alert"><%= alert %></p><% end %>

# devise 用のビューファイルを generate 
$ rails g devise:views

# devise 用の user モデルを generate
$ rails g devise user

# rails 5 系の場合 protect_form_forgery を修正
# https://goo.gl/C7ME1e
# https://github.com/plataformatec/devise#controller-filters-and-helpers
$ vi app/controllers/application_controller.rb
> protect_from_forgery prepend: true

# コントローラは存在しない場合 devise の vendor が参照される
# 継承してカスタムしたい場合には以下で作成可能
# 作成後 routes にて controllers: {} で sessions: 'admin/sessions' とか
$ rails g devise:controllers users

# ルーティング設定
$ vi config/routes.rb
```

#### /sign_in を /login にするなどのパス調整
```rb
# config/routes.rb
devise_for :users, path_names: {
  sign_up: 'signup', 
  sign_in: 'login', 
  sign_out: 'logout', 
  password: 'password',
  confirmation: 'confirmation', 
  unlock: 'unlock', 
  registration: 'register',
}
```

#### 名前空間を分けたりルーティングをいじったり
> [devise_for](https://www.rubydoc.info/github/plataformatec/devise/master/ActionDispatch/Routing/Mapper%3Adevise_for)  
> [Configuring views](https://github.com/plataformatec/devise#configuring-views)  
> [Configuring controllers](https://github.com/plataformatec/devise#configuring-controllers)  
> [Railsでdeviseひとつで複数モデルを管理しよう](https://qiita.com/Yama-to/items/54ab4ce08e126ef7dade)  
> [Deviseでdevise_forをネストした場所に書く方法](https://www.tmp1024.com/programming/post-4661)  

```rb
# config/routes.rb
# /admin/sign_in みたいなのを想定している
# この場合 :authenticate_user! メソッドは :authenticate_admin_user! となる

namespace :admin do
  devise_for :users, path: ''
end
```

```sh
# views を作成
$ rails g devise:views admin

# ( 必要ならば ) カスタム用 controller も領域を区切って作成
$ rails g devise:controllers admin
```

#### 既存 User モデルに適用する場合
```rb
# config/routes.rb にてルーティングを追加
devise_for :users

__END__

# カスタムしたい場合は以下を参考に ...
                  Prefix Verb   URI Pattern                    Controller#Action
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
           user_password PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
                         POST   /users/password(.:format)      devise/passwords#create 
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
       user_registration PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy
                         POST   /users(.:format)               devise/registrations#create
```

```rb
# user.rb 定義内にて devise にて利用モジュールを追加
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end
```

```rb
# users のマイグレーションを以下を参考に書き換え
# generate で生成される db/migrate/00000000000000_devise_create_users.rb

class DeviseCreateUsers < ActiveRecord::Migration[5.1]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.inet     :current_sign_in_ip
      t.inet     :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end
```


### How to use?
> [Controller filters and helpers](https://github.com/plataformatec/devise#controller-filters-and-helpers)  
> [ログイン後にマイページに飛ばす](http://tsumazuki.hatenadiary.jp/entry/2013/08/15/033130)

因みに `current_user` は当たり前だけどコントローラ / ビュー層でしか使えないからね。

```rb
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  # フィルターで「認証検査」して :only で認証領域を限定
  before_action :authenticate_user!, only: :index
  
  def index
    # current_user にログイン済みユーザが入る
    # admin とかプレフィクスつけたら current_admin_user とかになる
    @user = current_user
  end

  # ログイン後にここにいけ
  # application_controller.rb の継承先に書いても効かない注意ので注意
  # モデルやロール毎に振り分けたい場合はここで分岐頑張るしかない
  protected def after_sign_in_path_for(resource)
    if resource == :admin_user
      admin_root_path
    else
      root_path
    end
  end

  # ログアウトしたらにここにいけ
  protected def after_sign_out_path_for(resource)
    if resource == :admin_user  # ログアウト時 resource には :user とかのシンボルがはいる
      new_admin_user_session_path
    else
      new_user_session_path
    end
  end
end
```

```erb
<!--
  devise によるログインや登録、ユーザ編集などのフォームでは
  devise_error_messages! メソッドでエラー内容が出力できる
-->
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>
  ...
```

```erb
<!-- よくあるログインしてたら ~ でしてなかったら ~ というやつ -->
<% if user_signed_in? %>
  <%= link_to 'logout', destroy_user_session_path, method: :delete %>
<% else %>
  <%= link_to 'sign up', new_user_registration_path  %>
  <%= link_to 'login', new_user_session_path  %>
<% end %>
```

#### Mailer
> [How To: Use custom mailer](https://github.com/plataformatec/devise/wiki/How-To:-Use-custom-mailer)  
> [rodrigoflores/multiple-mailer](https://github.com/rodrigoflores/multiple-mailer/blob/master/app/models/user.rb#L6)  
> [Rails Devise でパスワードリセットなどのメールテンプレート(Mailer ビュー)をカスタマイズ](https://easyramble.com/customize-mail-template-of-devise.html)

デフォルトのままだと `Devise::Mailer` で送った URL はスコープ対応してくれないファック。

```rb
# config/initializers/devise.rb

config.mailer = 'UsersMailer'
config.parent_mailer = 'ApplicationMailer'
```

```rb
# app/mailers/users_mailer.rb

class UsersMailer < Devise::Mailer
  helper :application
  include Devise::Controllers::UrlHelpers
  default template_path: 'users_mailer'

  # def confirmation_instructions(record, token, opts={})
  #   super
  # end

  # def email_changed(record, token, opts={})
  #   super
  # end

  # def password_change(record, token, opts={})
  #   super
  # end

  # def reset_password_instructions(record, token, opts={})
  #   super
  # end

  # def unlock_instructions(record, token, opts={})
  #   super
  # end
end
```

```erb
<!-- app/views/users_mailer/reset_password_instructions.html.erb -->
<!--
  オーバライドしたいビューを作成してカスタム
  $ rails g devise:views で作ったビューでは複数スコープに対応していないため
  以下の link_to に入る URL メソッドは edit_password_url だが、ここで
  edit_users_user_password_url とか edit_admin_user_password_url とかに変更すれば
  スコープを考慮した URL を吐いてくれる
  confirmation_url は admin_user_confirmation_url(nil, ...) で動いた
  user_hoge_url メソッドは既に古い実装のようで @resource を食わせると変な URL 吐くので
  第一引数を nil にするのがポイント
-->

<% if @resource.is_admin %>
  <%= link_to 'Change my password', edit_admin_user_password_url(nil, reset_password_token: @token) %>
<% else %>
  <%= link_to 'Change my password', edit_user_password_url(nil, reset_password_token: @token) %>
<% end %>
```

```yml
# config/locales/devise.en.yml

en:
  devise:
    # メール件名のロケールはこのへん
    # 本文とかもここに body: とかで突っ込むとよいかも
    mailer:
      confirmation_instructions:
        subject: "Confirmation instructions"
      reset_password_instructions:
        subject: "Reset password instructions"
      unlock_instructions:
        subject: "Unlock instructions"
      email_changed:
        subject: "Email Changed"
      password_change:
        subject: "Password Changed"
```

#### SessionsController
> [Module: Devise::Controllers::SignInOut](https://www.rubydoc.info/github/plataformatec/devise/Devise/Controllers/SignInOut)

ユーザが `role` カラムや関連モデルを持っていて email ( unique id ) + password 以外のログイン検査が必要な場合は SessionsController で各メソッドをオーバライドしてあげる。

```rb
# app/controllers/admin/sessions_controller.rb

class Admin::SessionsController < Devise::SessionsController
  before_action :verify_admin_user, only [:create]
    
  protected def verify_admin_user
    if current_admin_user.role != 'admin'
      sign_out_all_scopes current_admin_user  # 手動ログアウト
      flash[:alert] = t 'devise.failure.invalid', authentication_keys: :email
      return redirect_to new_admin_user_session_url  # ログイン画面へ行け
    end
  end
end
```

#### RegistrationsController
> [deviseを使ってUserモデルに一対一のリレーションシップでユーザー情報を紐付け](https://qiita.com/chokosuki4400/items/30340d149c7cb7abf41b)  
> [devise_parameter_sanitizer.permit](devise_parameter_sanitizer.permit)  
> [Rails Devise registration with an additional model](https://stackoverflow.com/questions/13670013/rails-devise-registration-with-an-additional-model?answertab=votes#tab-top)

登録処理周りは registrations_controller が担当している。初期値の入れ込み、子モデルの同時登録、リダイレクト先変更など、案件によってカスタムしないとならない場所。以下は `user` モデルの新規作成時には、同時に `company` のような子モデルを登録する必要があるケースを考慮 ( このあたりのアソシエーションやバリデーション設定は済んでいる前提 )。

```rb
# registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  # devise デフォルトの user モデルカラム ( provider や email など ) 以外に
  # user モデルにカラム、同時登録する子モデル ( company ) がある場合は
  # before_action フィルタへのカスタムメソッドを登録してごにょごにょする
  before_action :configure_sign_up_params, only: [:create]
  before_action :configure_account_update_params, only: [:update]

  def new
    resource = build_resource({})
    resource.build_company  # user.company.build みたいなことみたい
    resource.company.plan_id = Plan.where({name: 'free'}).first.id
    respond_with resource
    # ここでいう resource は user モデルと思ってよい
    # new のフォームに流す初期値セットなんかはこのへんで
  end

  # edit / update / destroy /cancel など必要なら同じ要領でオーバライド
  # def edit
  #   super
  # end
  # 
  # def update
  #   super
  # end
  # 
  # def destroy
  #   super
  # end
  # 
  # def cancel
  #   super
  # end

  # Sign up 時に同時登録するカラム・子モデルを追加設定
  # 必要なカラム全て設定しないと登録されたような挙動で DB に NULL が入ってしまう
  protected def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up) do |params|
      params.permit(
        :name, :email, :password, :password_confirmation,
        company_attributes: [
          :user_id, :plan_id, :name, :address, :email,
          :phone, :website, :country, :history
        ]
      )
    end
  end

  # こっちは更新時
  def configure_account_update_params
    devise_parameter_sanitizer.permit(:account_update) do |params|
      params.permit(
        :name, :email, :password, :password_confirmation, :current_password,
        company_attributes: [
          :user_id, :plan_id, :name, :address, :email,
          :phone, :website, :country, :history
        ]
      )
    end
  end

  # Sign up 完了時の遷移先
  # confirmable 時はこちらではなく confirmations_controller の方が優先される
  # また Sign up 完了時点では認証領域外 ( ログイン画面とか ) に飛ばす思想みたいで
  # 自動ログインとかはしてくれない ... あたり前か
  def after_sign_up_path_for(resource)
    # super(resource)
    root_path
  end

  # Sign up → メール confirm 前など非アクティブ時点の遷移先
  def after_inactive_sign_up_path_for(resource)
    # super(resource)
    new_user_session_path
  end

  # 更新処理後のリダイレクト先
  def after_update_path_for(resource)
    root_path
  end
end
```

```rb
# models/user.rb

# fields_for を使うのでネストを許可
accepts_nested_attributes_for :company
```

```rb
<%= f.fields_for :company do |cf| %>
  <div class="form-group">
    <%= cf.label :name %>
    <%= cf.text_field :name, class: 'form-control', required: true %>
  </div>
<% end %>
```


------


## OAUTH ##
> [omniauth/omniauth - github.com](https://github.com/omniauth/omniauth)

[OAuth](https://qiita.com/TakahikoKawasaki/items/e37caf50776e00e733be) のための gem である omniauth と連携設定をすることで、各種 WEB サービスと連携した OAuth 認証が実現できる。

### Refs
- [定番gem「OmniAuth」活用法](https://codezine.jp/article/detail/10970?mode=print)
- [RailsでいろんなSNSとOAuth連携/ログインする方法](https://qiita.com/awakia/items/03dd68dea5f15dc46c15)
- [OmniAuthによるTwitter/FacebookのOAuth認証を実装](https://qiita.com/wtb114/items/a617474f1d31fa9e7c53)
- [deviceなしで Google OAuth 認証のサインインの実装](https://qiita.com/daijiro_maeyama/items/8b672ec0721d43f2d044)


### Install
`admin` 名前空間における `user` モデルの Facebook との OAuth を想定。

```sh
$ vi Gemfile
> gem 'omniauth'
> gem 'omniauth-facebook'

$ bundle install

# user に provider と uid を追加 ( ユーザ名なども取れるので必要ならば追加 )
$ rails g migration AddColumnsToUsers provider:string uid:string
$ rake db:migrate

# .env に連携サービスに登録したアプリの ID / Secret を追加
$ vi .env
> FACEBOOK_APP_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
> FACEBOOK_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxx

# devise.rb に omniauth 連携設定を追加
$ vi config/initializers/devise.rb
> config.omniauth :facebook,
>   ENV.fetch('FACEBOOK_APP_ID'), ENV.fetch('FACEBOOK_APP_SECRET'),
>   scope: 'email'  # 外部アカウントから取得したい情報の権限設定 ( サービスにより異なる )

# API からのリダイレクトを受け付けるコントローラを生成
$ rails g devise:controllers users
> ...
> create app/controllers/users/omniauth_callbacks_controller.rb  # こいつが API コールバック用

# devise の omniauth を user モデルに割り当て & ルーティング設定

# モデルは既存の :database_authenticatable の後に追加すれば OK
$ vi app/models/user.rb
> devise :omniauthable, omniauth_providers: [:facebook]

# ルーティングで devise_for :users にしたならもうルーティングされてるかも
$ vi app/config/routes.rb
$ rake routes  # 確認して /users/auth/facebook /users/auth/facebook/callback があれば OK
```

#### Link view
ログインボタンのビューを調整。各 SNS サービスで JavaScript による [ログインボタン - Facebook](https://developers.facebook.com/docs/facebook-login/web/login-button?locale=ja_JP) みたいなのが公開されているので、そっちを使う手段もある。但しその場合は自分で devise との連携を考えないといけないのでこっちのが楽。

```erb
# app/views/shared/_links.html.erb
# omniauth_authorize_path メソッドを修正 ( 名前空間を区切ったりすると修正が必要ぽい )
<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%=
      link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}",
      public_send("admin_user_#{provider.to_s}_omniauth_authorize_path")
    %>
    <br />
  <% end -%>
<% end -%>
```

#### User Model
```rb
# models/user.rb
class User < ApplicationRecord
  # 認証時にコールされる find_or_create_for_oauth メソッドをオーバライドしてカスタム
  def self.find_or_create_for_oauth(auth)
    find_or_initialize_by email: auth.info.email do |user|
      if user.persisted?
        return user
        # 既存ユーザなら user を変更せず return してブロックを中断
      end
      user.provider = auth.provider
      user.uid      = auth.uid
      user.name     = auth.info.name
      user.email    = auth.info.email
      # 新規ユーザなら OAuth で返却された情報をセット
      # このタイミングで password までセットして即ログインさせる手もある
      # 但しその場合当該ユーザへのパスワード通知どうする問題がある
      # また confirmable: true にしている場合メールアドレス登録処理が挟まる
      # この段階ではパスワード未セットで、後にフォーム入力を挟む方が無難な気がする
    end
  end

  def self.new_with_session(params, session)
    if user_attributes = session['devise.user_attributes']
      new(user_attributes) { |user| user.attributes = params }
    else
      super
    end
  end
end
```

#### Callback Controller
```rb
# omniauth_callbacks_controller.rb
# OAuth 認証処理をカスタム

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  # Facebook からのコールバックを有効化
  def facebook
    callback
  end

  # 各サービスで共通利用するコールバックメソッドを定義
  private def callback
    @user = User.find_or_create_for_oauth(request.env['omniauth.auth'])
    # 上記 request.env['omniauth.auth'] にサービスから提供されたユーザ情報が入る
    if @user.new_record?
      session['devise.user_attributes'] = @user.attributes
      redirect_to new_admin_user_registration_url
      # 新規なら find_or_initialize_by で new した空ユーザモデルを session にセット
      # 登録フォームリダイレクト → ここまでにセットした値がビュー form_helper に反映される
      # 追加でパスワードやユーザ関連子モデルの情報入力をさせて create へ POST で登録完了
    else
      sign_in_and_redirect @user
      # 既存ユーザならログイン実行 & リダイレクト
    end
  end
end
```

### ルーティングの手動設定
> [Wrong OmniAuth configuration. If your are getting this exception... message](https://github.com/omniauth/omniauth-saml/issues/129)  
> [Devise+Omniauth, routes versioning](https://stackoverflow.com/questions/24574094/deviseomniauth-routes-versioning)  
> [deviseでOAuth認証後のログイン先を変更する方法](https://notsleeeping.com/archives/2487)

users が複数ロールを兼ねていて `has_one :admins` だったり `has_one :members` だったりするような構成で、 devise で複数の `namespace` に跨った認証を実装しているようなケース ( `current_admin_user` とか `current_user` とかの devise scope が存在するようなケース ) において、omniauth 連携箇所についてだけは **必ずモデルに対して単一のルーティング ( URI ) しか設けられない** 点に注意。またコールバック受け時の実処理を記載するコントローラ `omniauth_callbacks` もモデルに対して 1 つしか実装できない。

よって通常ユーザ `/users` と管理者ユーザ `/admin/users` のようなルーティングを実装していたとしても omniauth による OAuth コールバック処理は同居させる必要がある。実際に `routes.rb` にて `:omniauthable` なモデルへ複数の `omniauth_callback` ルーティングを設定しようとすると以下のようなエラーになる。

```
`set_omniauth_path_prefix!': Wrong OmniAuth configuration. If you are getting this exception, it means that either: (RuntimeError)

1) You are manually setting OmniAuth.config.path_prefix and it doesn't match the Devise one
2) You are setting :omniauthable in more than one model
3) You changed your Devise routes/OmniAuth setting and haven't restarted your server
```

このような場合は `devise_for` によるルーティングのうち `:omniauth_callbacks` を `skip` して手動でルーティングしてあげる必要がある。

```rb
# config/routes.rb

# Manually setting for omniauth callbacks.
devise_for :users, path: '/',
  path_names: {
    sign_up: 'signup',
    sign_in: 'login',
    sign_out: 'logout',
    password: 'password',
    unlock: 'unlock',
    registration: 'register',
    confirmation: 'confirmation',
  },
  controllers: {
    omniauth_callbacks: 'omniauth_callbacks',  # こいつを user / admin で共用
    confirmations: 'confirmations',
    passwords: 'passwords',
    registrations: 'registrations',
    sessions: 'sessions',
  }

# Admin layer.
namespace :admin do
  devise_for :users, skip: [:omniauth_callbacks]  # 既に共用があるので skip する
end
```

```rb
# config/initializers/devise.rb

# Manually setting for omniauth callbacks.
config.omniauth_path_prefix = '/auth'
```

```erb
<!-- _links.html.erb -->
<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%=
      link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}",
      public_send("user_#{provider.to_s}_omniauth_authorize_path"),
      onclick: "document.cookie = 'role=admin; path=/'"  # コールバックで利用
    %>
    <br />
  <% end -%>
<% end -%>
```

```rb
# app/controllers/omniauth_callbacks_controller.rb

class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    @user = User.find_or_create_for_oauth(request.env['omniauth.auth'])
    if @user.new_record?
      # 適当に OAuth ログインボタンの onClick に Cookie セットとか仕込んで
      # このタイミングで user なのか admin なのかでリダイレクト先を切り分け
      # 恐らくリダイレクト先は新規登録なので new_user_registration_url みたいなやつ
    else
      # こちらはそのままログインさせる分岐だが、sign_in_and_redirect に引数を渡さないで
      # スコープを自動判別させた場合、このままだと全てのロールが同じ user スコープになる
      # ( admin だろうが user だろうが全て current_user などの扱いになってしまう ) 
      # よって上記と同様に Cookie などで事前にどの分岐から来たのか印をつけておき
      # 、このタイミングで「どのスコープでログインさせるか」を手動で設定させるのがよい
      case cookies.fetch('role')
      when 'admin'
        cookies.delete('role')
        sign_in(:admin_user, @user)
        redirect_to after_sign_in_path_for(@user)  # スコープありな admin_user
      else
        cookies.delete('role')
        sign_in_and_redirect @user  # スコープなし user
      end
    end
  end
end
```

```sh
$ rails routes
                          Prefix Verb     URI Pattern                                                                              Controller#Action
                            root GET      /                                                                                        feeds#root
                new_user_session GET      /login(.:format)                                                                         sessions#new
                    user_session POST     /login(.:format)                                                                         sessions#create
            destroy_user_session GET      /logout(.:format)                                                                        sessions#destroy
user_facebook_omniauth_authorize GET|POST /auth/facebook(.:format)                                                                 omniauth_callbacks#passthru
 user_facebook_omniauth_callback GET|POST /auth/facebook/callback(.:format)                                                        omniauth_callbacks#facebook
               new_user_password GET      /password/new(.:format)                                                                  passwords#new
              edit_user_password GET      /password/edit(.:format)                                                                 passwords#edit
                   user_password PATCH    /password(.:format)                                                                      passwords#update
                                 PUT      /password(.:format)                                                                      passwords#update
                                 POST     /password(.:format)                                                                      passwords#create
        cancel_user_registration GET      /register/cancel(.:format)                                                               registrations#cancel
           new_user_registration GET      /register/signup(.:format)                                                               registrations#new
          edit_user_registration GET      /register/edit(.:format)                                                                 registrations#edit
               user_registration PATCH    /register(.:format)                                                                      registrations#update
                                 PUT      /register(.:format)                                                                      registrations#update
                                 DELETE   /register(.:format)                                                                      registrations#destroy
                                 POST     /register(.:format)                                                                      registrations#create
           new_user_confirmation GET      /confirmation/new(.:format)                                                              confirmations#new
               user_confirmation GET      /confirmation(.:format)                                                                  confirmations#show
                                 POST     /confirmation(.:format)                                                                  confirmations#create
         new_admin_user_session GET      /admin/login(.:format)                                                                  admin/sessions#new
             admin_user_session POST     /admin/login(.:format)                                                                  admin/sessions#create
     destroy_admin_user_session GET      /admin/logout(.:format)                                                                 admin/sessions#destroy
        new_admin_user_password GET      /admin/password/new(.:format)                                                           admin/passwords#new
       edit_admin_user_password GET      /admin/password/edit(.:format)                                                          admin/passwords#edit
            admin_user_password PATCH    /admin/password(.:format)                                                               admin/passwords#update
                                 PUT      /admin/password(.:format)                                                               admin/passwords#update
                                 POST     /admin/password(.:format)                                                               admin/passwords#create
 cancel_admin_user_registration GET      /admin/register/cancel(.:format)                                                        admin/registrations#cancel
    new_admin_user_registration GET      /admin/register/signup(.:format)                                                        admin/registrations#new
   edit_admin_user_registration GET      /admin/register/edit(.:format)                                                          admin/registrations#edit
        admin_user_registration PATCH    /admin/register(.:format)                                                               admin/registrations#update
                                 PUT      /admin/register(.:format)                                                               admin/registrations#update
                                 DELETE   /admin/register(.:format)                                                               admin/registrations#destroy
                                 POST     /admin/register(.:format)                                                               admin/registrations#create
    new_admin_user_confirmation GET      /admin/confirmation/new(.:format)                                                       admin/confirmations#new
        admin_user_confirmation GET      /admin/confirmation(.:format)                                                           admin/confirmations#show
                                 POST     /admin/confirmation(.:format)                                                           admin/confirmations#create
```

以上是关于markdown [rails:devise] Ruby on Rails的身份验证gem。 #ruby #rails的主要内容,如果未能解决你的问题,请参考以下文章

Rails:如何在 Rails 中为 Devise 设置密钥?

Rails - 设计/Omniauth - 无方法错误配置

无法在 Rails 中使用 Devise 销毁会话 [重复]

Heroku/Rails/Devise:你想要的改变被拒绝了

Rails & Devise:如何在没有布局的情况下呈现登录页面?

Rails 和 Devise 的强大参数