Rails開発でsorceryを利用したユーザー認証を行う
「今更改めて Ruby On Railsの開発を行う 〜 Part9」です。今までRails開発の認証にはdeviceを利用していましたが、今回はsorceryを利用してユーザー認証をしていきたいと思います。
[ 目次 ]
はじめに
こんにちは、香港に住んでいるWEBデベロッパーのなかむ(@nakanakamu0828)です。
この記事は過去に運用していたブログからの移行記事になります。
今回はSorcery/sorceryを利用したユーザー認証処理を実装していきます。
sorceryインストール
まずはGemfile
にsorcery
の設定を追加します
# Gemfile
gem 'sorcery'
続いてbundle install
コマンドを実行し、ライブラリをインストールしてください。
$ bundle install
or
$ bundle install --path=vendor/bundle
※ 環境に合わせてプロジェクト配下にインストールするかどうかでコマンドを選択してください。
sorceryのセットアップ
sorceryのテンプレートを作成します
$ rails g sorcery:install
create config/initializers/sorcery.rb
generate model User --skip-migration
invoke active_record
create app/models/user.rb
insert app/models/user.rb
insert app/models/user.rb
create db/migrate/20180305075527_sorcery_core.rb
※ 作成されたファイルを確認してみてください。
続いてDBをセットアップします。
migrationを利用してuserテーブルを作成します。
$ rails db:migrate
ユーザーの新規登録
それではユーザーの新規登録機能を作成していきましょう。
rails
コマンドを利用してcontrollerを作成します。
$ rails g controller users/registrations
new
メソッドで新規登録画面の表示を行い、create
メソッドで登録します。app/controllers/users/registrations_controller.rb
を以下のように修正してください。
# app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < ApplicationController
def new
@user = User.new
end
def create
@user = User.new(user_params)
respond_to do |format|
if @user.save
format.html { redirect_to root_url, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: @user }
else
format.html { render :new }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
Userモデルにはバリデーションを追加します。
# app/models/user.rb
class User < ApplicationRecord
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
続いてviewを用意していきます。
以下のコマンドを実行してディレクトリとファイルを用意しましょう。
今回もfrontend
ディレクトリ配下にコンポーネントとして用意していきます。
$ mkdir -p app/views/users/registrations
$ touch app/views/users/registrations/new.html.erb
$ mkdir -p frontend/pages/user/registration
$ touch frontend/pages/user/registration/_new.html.erb
app/views/users/registrations/new.html.erb
ファイルは以下のようにコンポーネントを呼び出すように設定してください。
<!-- app/views/users/registrations/new.html.erb -->
<%= render "layouts/site/site" do %>
<%= render "pages/user/registration/new" %>
<% end %>
コンポーネントの_new.html.erb
には、登録フォームをコーディングしていきます。
<!-- frontend/pages/user/registration/_new.html.erb -->
<div class="user-registration">
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-half is-offset-one-quarter">
<div class="box">
<h1 class="title has-text-centered is-size-4">Signup</h1>
<hr>
<%= form_for(@user, url: users_registrations_path, html: { medhod: :post }) do |f| %>
<% if @user.errors.any? %>
<div class="notification is-danger">
<ul>
<% @user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<label class="label">Email</label>
<div class="control has-icons-left">
<%= f.email_field :email, class: "input", placeholder: 'Email', autocomplete: :off %>
<span class="icon is-small is-left">
<i class="fas fa-envelope"></i>
</span>
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control has-icons-left">
<%= f.password_field :password, class: "input", placeholder: 'Password', autocomplete: :off %>
<span class="icon is-small is-left">
<i class="fas fa-key"></i>
</span>
</div>
</div>
<div class="field">
<label class="label">Confirm Password</label>
<div class="control has-icons-left">
<%= f.password_field :password_confirmation, class: "input", placeholder: 'Confirm Password', autocomplete: :off %>
<span class="icon is-small is-left">
<i class="fas fa-key"></i>
</span>
</div>
</div>
<hr>
<div class="field is-grouped is-grouped-centered">
<input type="submit" class="button is-primary is-fullwidth" value="Submit">
</div>
<% end %>
</div>
</div>
</div>
</div>
</section>
</div>
【補足】
ヘッダーに用意しているロケーション切り替えのselectが原因で、新規登録でバリデーションに引っかかった場合、500エラーが出てしまいました。
メニュー部分は以下のように修正しました。
<!-- frontend/layouts/site/_site.html.erb -->
<option data-url="<%= url_for Rails.application.routes.recognize_path(request.url).merge({ only_path: false, locale: locale }) %>"<%= I18n.locale == locale ? ' selected' : '' %>><%= locale %></option>
↓
<option data-url="<%= url_for(Rails.application.routes.recognize_path(request.get? ? request.url : url_for(:back)).merge({ only_path: false, locale: locale })) %>"<%= I18n.locale == locale ? ' selected' : '' %>><%= locale %></option>
バリデーションエラーを多言語化する為、翻訳ファイルを作成します。
$ touch config/locales/models/ja.yml
config/locales/models/ja.yml
は以下のような内容にしてください。
ja:
activerecord: &activerecord
models:
user: "ユーザー"
attributes:
user:
id: "ID"
email: "メールアドレス"
crypted_password: "パスワード"
salt: "Salt"
created_at: "登録日時"
updated_at: "更新日時"
errors:
models:
user:
attributes:
email:
blank: "が入力されていません。"
taken: "は既に登録されています。"
password:
blank: "が入力されていません。"
too_short: "が短すぎます。"
password_confirmation:
blank: "が入力されていません。"
confirmation: "がパスワードと一致しません"
ルーティングファイル(config/routes.rb
)に以下のような新規登録ページへのルーティングを追記します。
# config/routes.rb
namespace :users, module: :users do
resource :registrations, only: [:new, :create], :path_names => { new: 'signup' }
end
以下のようなlink_toメソッドを利用することで、新規登録ページのリンクが作成されます。適した場所にリンクを配置してください。
<%= link_to 'signup', new_users_registration_path %>
今回はヘッダーにリンクを追加します。frontend/layouts/site/_site.html.erb
を修正します。
<!-- frontend/layouts/site/_site.html.erb -->
<div id="navbarMenuHeroB" class="navbar-menu">
<div class="navbar-end">
<a class="navbar-item is-active">
Home
</a>
<!-- 以下のリンクを追加 -->
<%= link_to 'singup', new_users_registration_path, class: 'navbar-item' %>
<!-- ここまで -->
<a class="navbar-item">
Login
</a>
<a class="navbar-item">
<i class="fas fa-shopping-cart"></i>
</a>
・・・
ここまでできたら新規登録画面は完了です。
サーバーを再起動して画面を確認してみましょう。
ユーザーログイン
次はログイン機能を実装していきます。
rails
コマンドを利用してcontrollerを作成します。
$ rails g controller users/sessions
new
メソッドでログイン画面の表示、create
メソッドでログイン処理を実装します。またsignout
メソッドを用意してログアウト処理を実装します。
app/controllers/users/sessions_controller.rb
を以下のように修正してください。
# app/controllers/users/sessions_controller.rb
class Users::SessionsController < ApplicationController
def new
@user = User.new
end
def create
@user = login(login_params[:email], login_params[:password])
if @user
redirect_back_or_to(root_path, notice: 'Login successful')
else
flash.now[:alert] = 'Login failed'
render action: 'new'
end
end
def signout
logout
redirect_to(root_path, notice: 'Logged out!')
end
private
def login_params
params.require(:user).permit(:email, :password)
end
end
続いてviewを用意していきます。
以下のコマンドを実行してディレクトリとファイルを用意しましょう。
新規登録と同様でfrontend
ディレクトリ配下にコンポーネントとして用意していきます。
$ mkdir -p app/views/users/sessions
$ touch app/views/users/sessions/new.html.erb
$ mkdir -p frontend/pages/user/session
$ touch frontend/pages/user/session/_new.html.erb
app/views/users/sessions/new.html.erb
ファイルは以下のようにコンポーネントを呼び出すように設定してください。
<!-- app/views/users/sessions/new.html.erb -->
<%= render "layouts/site/site" do %>
<%= render "pages/user/session/new" %>
<% end %>
コンポーネントの_new.html.erb
には、ログインフォームをコーディングしていきます。
<!-- frontend/pages/user/sessions/_new.html.erb -->
<div class="user-session">
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-half is-offset-one-quarter">
<div class="box">
<h1 class="title has-text-centered is-size-4">Signin</h1>
<hr>
<%= form_for(@user ||= User.new, url: users_sessions_path, html: { medhod: :post }) do |f| %>
<% if @user.errors.any? %>
<div class="notification is-danger">
<ul>
<% @user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<label class="label">Email</label>
<div class="control has-icons-left">
<%= f.email_field :email, class: "input", placeholder: 'Email', autocomplete: :off %>
<span class="icon is-small is-left">
<i class="fas fa-envelope"></i>
</span>
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control has-icons-left">
<%= f.password_field :password, class: "input", placeholder: 'Password', autocomplete: :off %>
<span class="icon is-small is-left">
<i class="fas fa-key"></i>
</span>
</div>
</div>
<hr>
<div class="field is-grouped is-grouped-centered">
<input type="submit" class="button is-primary is-fullwidth" value="Submit">
</div>
<% end %>
</div>
</div>
</div>
</div>
</section>
</div>
ルーティングファイル(config/routes.rb
)に以下のようなログインページとログアウトのルーティングを追記します。
# config/routes.rb
namespace :users, module: :users do
resource :registrations, only: [:new, :create], :path_names => { new: 'signup' }
### ここから追加
resource :sessions, only: [:new, :create], :path_names => { new: 'signin' } do
get :signout, to: 'sessions#signout', as: :signout
end
### ここまで
end
登録と同様でヘッダーのリンクを追加しましょう。frontend/layouts/site/_site.html.erb
を修正します。
<!-- frontend/layouts/site/_site.html.erb -->
<div id="navbarMenuHeroB" class="navbar-menu">
<div class="navbar-end">
<a class="navbar-item is-active">
Home
</a>
<% if current_user %>
<%= link_to 'Singout', signout_users_sessions_path, class: 'navbar-item', method: :delete %>
<% else %>
<%= link_to 'Singup', new_users_registrations_path, class: 'navbar-item' %>
<%= link_to 'Singin', new_users_sessions_path, class: 'navbar-item' %>
<% end %>
<a class="navbar-item">
<i class="fas fa-shopping-cart"></i>
</a>
・・・
ここまでできたらログイン/ログアウトの実装は完了です。
サーバーを再起動して画面を確認してみましょう。
今回の成果物は こちら をご確認ください。
番外編
現在作成デモとして作成しているサイトはサブドメインで言語切り替えを行っています。その場合、デフォルトではサブドメイン毎にセッションが張られます。なので、日本語ページでログインしていても英語ページでは未ログインになってしまいます。
これを回避する為にはconfig/initializers/session_store.rb
ファイルを作成し、以下のように設定してください。
# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store, key: '_netshop_session', :domain => :all
※ キーの名称は各自のアプリに合ったものを設定する