Rails+Sorceryで認証処理を実装する

RailsによるWebアプリケーションにおいて、Sorceryを使ってユーザ管理と認証処理を実現する方法について纏めます。

Sorceryとは

Sorceryは、ユーザ認証機能を簡単に実装できるライブラリで、MITライセンスのオープンソースソフトウェアとして公開されています。

ユーザの認証の基本的な機能であるパスワード認証を始め、

  • User Activation
  • Reset Password
  • Remember Me
  • Session Timeout
  • Brute Force Protection
  • Basic HTTP Authentication
  • Activity Logging

といった機能が揃っていて、必要な機能を選んで使うことができます。

パスワード認証を実装する

ここでは、Sorceryの基本的な機能であるパスワード認証の実装方法について纏めます。

パスワード認証機能を作るにあたり、

  • ユーザー登録機能
  • ログイン機能
  • ログアウト機能

を実際に作ってみます。ログイン機能で、Sorceryが提供するパスワード認証機能を使用します。

環境

この記事を書くにあたって、次の環境を使用しました。

  • Ruby 2.3.3(rbenv)
  • Rails 5.0.1
  • SQLite3 3.8.5
  • Sorcery 0.10.2

Sorceryを導入する

RailsアプリケーションのGemfileにsorceryを追加し、bundle installします。

gem 'sorcery'

Userモデルを作成する

Sorceryが提供するジェネレータbundle exec rails g sorcery:installを実行し、Userモデルとデータベースのmigrationを生成します。

次のファイルが作成されます。

  • app/models/user.rb
  • config/initializers/sorcery.rb
  • db/migrate/yyyymmddhhmmss_sorcery_core.rb

bundle exec rails db:migrateを実行するとusersテーブルが生成され、次のようなスキーマとなっています。

#db/schema.rb抜粋
  create_table "users", force: :cascade do |t|
    t.string   "email",            null: false
    t.string   "crypted_password"
    t.string   "salt"
    t.datetime "created_at",       null: false
    t.datetime "updated_at",       null: false
    t.index ["email"], name: "index_users_on_email", unique: true
  end

また、出来上がったUserモデルは、次のようなコードとなっています。

#app/models/user.rb
class User < ApplicationRecord
  authenticates_with_sorcery!
end

User登録機能を実装する

出来上がったUserモデルをベースとして、User登録機能を実装していきます。

モデルの実装

はじめに、Userモデルのフィールドである、

  • email
  • password
  • password_confirmation

に対するバリデーションを実装します。バリデーションの内容は、Sorceryのチュートリアルのママとしてみます。したがって、次のような実装となります。

# app/models/user.rb
class User < ActiveRecord::Base
  authenticates_with_sorcery!

  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  validates :email, uniqueness: true
end

コントローラの実装

次に、User登録を実現するためのコントローラを実装します。

bundle exec rails g controller users new createを実行し、app/controllers/users_controller.rbを作成します。

ここでは、ユーザ登録ページを表示するnewアクションと、ユーザ登録処理を行うcreateアクションを生成しており、それらに対し、次のようなルーティングを定義します。

#config/routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  resources :users, only: [:new, :create]
end

また、コントローラは、次のような実装としました。

#app/controllers/users_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to welcome_path
    else
      render :new
    end
  end

  private

  def user_params
    params.require(:user).permit(
      :email,
      :password,
      :password_confirmation,
    )
  end
end

ここで、createアクションの中で、Userレコード登録が成功した際のリダイレクト先として、welcome_pathを指定しています。これは、ログイン画面へのパスであり、この時点で未定義なため、このままではRoutingエラーとなってしまう点に注意が必要です。ログイン画面へのルーティングは後述します。

ビューの実装

User登録のための画面を、次のようなテンプレートで実装します。

#app/views/users/new.html.slim
h1 Users#new
= form_for(@user) do |f|
  div
    = f.label :email
    = f.text_field :email
  div
    = f.label :password
    = f.password_field :password
  div
    = f.label :password_confirmation
    = f.password_field :password_confirmation
  div
    = f.submit

ログイン画面へのルーティング

最後にログイン画面へのルーティングとログイン画面を実装すると、User登録機能としては一通り動作することになります。

ログイン画面は、SessionControllerというコントローラを作成することで実現します。bundle exec rails g controller sessions new create destroyを実行し、コントローラを作成します。

また、ルーティングの定義は、次のように書きます。

#config/routes.rb
  get    '/welcome', to: "sessions#new",     as: :welcome
  post   '/login',   to: "sessions#create",  as: :login
  delete '/logout',  to: "sessions#destroy", as: :logout

これにより、welcome_pathヘルパが生成され、ログイン画面へのルーティングが可能となり、ユーザ登録機能が実装できました。なお、ここまでの実装では、Sorceryそのものが提供する機能は使っていません。

次は、Sorceryを使った認証処理を実装していきます。

認証機能を実装する

ここから、Sorceryを使って認証処理を実装していきます。

ここでは、次のようなシナリオを考えてみます。

  • 認証が必要なページへアクセスした場合
    • 認証済みの場合
      • 要求されたページを応答する。
    • 認証されていない場合
      • ログインページを応答する。
  • 認証が不要なページへアクセスした場合
    • 要求されたページを応答する。

これを実現するにあたり、さきほど定義したルーティング、つまり、

#config/routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  get    '/welcome', to: 'sessions#new',     as: :welcome
  post   '/login',   to: 'sessions#create',  as: :login
  delete '/logout',  to: 'sessions#destroy', as: :logout
  resources :users, only: [:new, :create]
end

に対して、認証が必要となるページへのルーティングを追加します。 認証が必要なページは、HomeControllerのindexアクションが該当するものとして、このコントローラをbundle exec rails g controller home indexを実行して作成します。また、home#indexをルートパスとして定義するため、config/routes.rbに以下を追加します。

root to: 'home#index'

この時点では、認証無しでどのページにもアクセスできる状態となっています。

認証済み判定処理

認証が必要なページがリクエストされた場合、認証済みか否かを判定する必要があります。 これを実現するには、Sorceryが提供するrequire_loginメソッドをbefore_actionに指定します。 これは、ApplicationControllerに書きます。 また、require_loginをbefore_actionに指定するにあたり、認証されていない場合の処理も合わせて実装する必要があります。 デフォルトでは、Sorceryは、not_authenticatedというメソッドを実行するため、この名前で実装します。ここでは、認証されていない場合は、ログインページへリダイレクトする実装としています。 (not_authenticated以外のメソッドを指定する場合は、config/initializers/sorcery.rbを編集して変更します。)

#app/controllers/application_controller.rb
before_action :require_login

protected

def not_authenticated
  redirect_to welcome_path
end

このままでは、ログインページ自体もbefore_actionが動作してしまうため、認証済み判定が不要なコントローラでは、before_actionをスキップします。 例えば、以下のコードは、SessionsControllerの実装です。

#app/controllers/sessions_controller.rb
  skip_before_filter :reqire_login, except: [:destroy]

ここまでの実装で、認証が必要なページへアクセスした場合に、認証がされていなければ、ログインページにリダイレクトする、という処理が実現できました。

認証処理(ログイン)

ここから、認証処理を実装していきます。

まず、ログインフォームを定義します。 ログインフォームは、メールアドレスとパスワードのフィールドを持つシンプルなフォームとして、次のような実装とします。

#app/views/sessions/new.html.slim
h1 Sessions#new
= form_tag login_path, method: :post do
  div
    = label_tag :email
    = text_field_tag :email
  div
    = label_tag :password
    = password_field_tag :password
  div
    = submit_tag "Login"

このフォームはSessionsController#createで処理します。

#app/controllers/sessions_controller.rb
  def create
    @user = login(params[:email], params[:password])
    if @user
      redirect_back_or_to(root_path)
    else
      render :new
    end
  end

ここで、Sorceryが提供するloginメソッドを使用しています。これだけで認証処理が実現できます。このメソッドにより、emailによるUser検索、パスワードの検証を行い、正常に処理できるとセッションデータにUserレコードのid値を格納する、という処理が行われます。 この実装により、認証に成功すると、HomeController#indexへリダイレクトされます。

ログアウト処理

最後にログアウト処理を実装してみます。 さきほど、生成したapp/views/home/index.html.slimを編集して、ログアウト用のリンクを追加します。

#app/views/home/index.html.slim
h1 Home#index
= link_to('Logout', logout_path, method: :delete)

ログアウト処理はSessionsController#destroyアクションで行います。 コントローラの実装は次のように行います。

#app/controllers/sessions_controller.rb
  def destroy
    logout
    redirect_to welcome_path
  end

ここでもSorceryが提供するメソッドを使用します。 logoutメソッドは、セッションをリセットする、という処理を行っています。


ここまでの実装により、Sorceryを使ったユーザ管理とパスワード認証の一通りの処理が実現できました。 書くべきコードは非常に少なくすみ、簡単に実装できることがわかります。

なお、この記事に記載したコードは、sglabs/rails_sorcery_exampleで公開していますので、参考にして頂ければ、と思います。