認証の習作: has_secure_password
外観
Action Cableの習作で作成したユーザ認証はログイン機能を省略していました。今回はログイン機能を中心にユーザ認証をフルスクラッチで作成したいと思います。
Rails 5.2の認証機能のチュートリアルを参考にさせていただきました。
環境
macOS 10.15.4
Ruby 2.7.1
Rails 6.0.3.1
Yarn 1.22.4
Node 13.12.0
参照
Authentication from Scratch with Rails 5.2 - Stefan Wintermeyer - Medium
Active Model の基礎 - Railsガイド > 1.11 SecurePasswordモジュール
リポジトリ
https://github.com/usutani/try_rails6_auth
アプリの準備
rails new -MT --skip-active-storage try_rails6_auth
cd try_rails6_auth
bin/rails g controller Home index
config/routes.rb
Rails.application.routes.draw do
root 'home#index'
end
app/views/home/index.html.erb
<p id="notice"><%= notice %></p>
<h1>Example</h1>
<p>Lorem ipsum dolor sit amet, consectetur</p>
has_secure_passwordの利用
Gemfile
# Use Active Model has_secure_password
gem 'bcrypt', '~> 3.1.7'
bundle install
ユーザの作成
bin/rails g scaffold User email:uniq password:digest
bin/rails db:migrate
app/models/user.rb
class User < ApplicationRecord
has_secure_password
validates :email, presence: true, uniqueness: true
end
bin/rails s
open http://localhost:3000/users/new
app/views/users/_form.html.erb
password_confirmationは省略可能。ある場合ない場合それぞれ適切に必須入力項目の確認が行われます。詳しくはRailsガイドを参照のこと。
セッションの作成
bin/rails g controller sessions new create destroy
app/controllers/sessions_controller.rb
session[:user_id]の代わりにcookies.encrypted[:user_id]を用います。
2021/12/04 追記 認証成功後 reset_session を呼ぶ。
Rails セキュリティガイド
最も効果的な対応策は、ログイン成功後に古いセッションを無効にし、新しいセッションidを発行することです。
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
reset_session
cookies.encrypted[:user_id] = user.id
redirect_to root_url, notice: 'Logged in!'
else
flash.now[:alert] = 'Email or password is invalid'
render 'new'
end
end
def destroy
cookies.delete(:user_id)
redirect_to root_url, notice: 'Logged out!'
end
end
app/views/sessions/new.html.erb
form_withに変更しました。
<p id="alert"><%= alert %></p>
<h1>Login</h1>
<%= form_with url: sessions_path, local: true do |form| %>
<div class="field">
<%= label_tag :email %>
<%= text_field_tag :email %>
</div>
<div class="field">
<%= label_tag :password %>
<%= password_field_tag :password %>
</div>
<div class="actions">
<%= submit_tag "Login" %>
</div>
<% end %>
補足: form_withでAjaxを用いる場合
https://github.com/usutani/try_rails6_auth/tree/remote-form_with
<%= form_with url: sessions_path do |form| %>
app/controllers/sessions_controller.rb
newからcreateに変更します。
else
flash.now[:alert] = 'Email or password is invalid'
render 'create'
end
touch app/views/sessions/create.js.erb
(() => {
const el = document.getElementById('alert')
el.innerText = '<%=j alert %>'
})()
ルーティング
config/routes.rb
Rails.application.routes.draw do
root 'home#index'
resources :users
resources :sessions, only: %i[new create destroy]
get 'signup', to: 'users#new', as: 'signup'
get 'login', to: 'sessions#new', as: 'login'
get 'logout', to: 'sessions#destroy', as: 'logout'
end
bin/rails s
open http://localhost:3000/login
open http://localhost:3000/logout
ユーザ名の表示
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
if cookies.encrypted[:user_id]
@current_user ||= User.find(cookies.encrypted[:user_id])
else
@current_user = nil
end
end
end
app/views/home/index.html.erb
<% if current_user %>
Logged in as <%= current_user.email %>.
<%= link_to "Log Out", logout_path %>
<% else %>
<%= link_to "Sign Up", signup_path %> or
<%= link_to "Log In", login_path %>
<% end %>
<p id="notice"><%= notice %></p>
<h1>Example</h1>
<p>Lorem ipsum dolor sit amet, consectetur</p>
以上です。
この記事が気に入ったらサポートをしてみませんか?