
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モデルのフィールドである、
- 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で公開していますので、参考にして頂ければ、と思います。