Help us understand the problem. What is going on with this article?

0から始める!ログイン/認証システムの実装 前編

はじめに

いつもユーザー機能を実装するときは「device」というgemを使っていましたが、0から作ることで全てを理解出来るのでカスタムしやすかったりします。
また、最近のRailsへの変更 (6.3) により、カスタム認証システムを容易に作成できるようになったらしいです。
既存のものを使うにしても、0から作れるような知識を持っておくことで何かあっても柔軟に対応できるので解説しようと思います。
※長くなるため、3編編成にしたいと思います。。
0から始める!ログイン/認証システムの実装 中編
0から始める!ログイン/認証システムの実装 後編

⓪最初の準備

1.usersコントローラーを作成する。(newアクションもセットで)

$ rails g controller users new

2.ルーティングを設定する

config/routes.rb
Rails.application.routes.draw do
  .
  .
  get  '/signup',  to: 'users#new'
end

①Userモデルの作成

1.Userモデルを作成する。

$ rails g model User name:string email:string

$ rails db:migrate

2.バリデーションを設定する。

基本的なバリデーションの「存在性・長さ・フォーマット」を指定し、必要に応じて細かく設定していく。
※正規表現については、Rubularという正規表現を試せるWebサイトがあるので試してみてください。

user.rb
class User < ApplicationRecord
  # 保存する前に全ての文字を小文字に変換
  before_save { self.email = email.downcase }

  # 名前が50文字以下かつ存在している
  validates :name, presence: true, length: { maximum: 50 }

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i  
  validates :email, presence: true, length: { maximum: 255 }, # 存在しているかつ255文字以内
                    format: { with: VALID_EMAIL_REGEX },    # メールアドレスのフォーマット(ドット(.)の連続はNG)
                    uniqueness: { case_sensitive: false }      # 大文字小文字関係なく一意性を確認
end

3.インデックスを貼る(今回はemailに貼ります)

これをすることで、最初のデータから最後まで検索する、全表スキャン (Full-table Scan) を防ぎ、効率的に検索してくれます。

$ rails g migration add_index_to_users_email
db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[5.1]
  def change
    add_index :users, :email, unique: true
  end
end

$ rails db:migrate

4.セキュアなパスワードの追加

各ユーザーにパスワードとパスワードの確認を入力させ、それを (そのままではなく) ハッシュ化したものをデータベースに保存します。

user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i  
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }

  # 以下の行を追加
  has_secure_password

  # 存在するかつ最低6文字以上のバリデーション追加
  validates :password, presence: true, length: { minimum: 6 }
end

「has_secure_password」を追加することで以下の機能が使用可能となります。

  • セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。

  • 2つペアの仮想的な属性 (passwordとpassword_confirmation) が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される。

  • authenticateメソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド) 。

これらを使うためにはUserモデル内に「password_digest」カラムを用意しなければならないので追加する。

$ rails g migration add_password_digest_to_users password_digest:string

$ rails db:migrate

5.bcryptをGemfileに追加する

has_secure_passwordを使ってパスワードをハッシュ化するためには、最先端のハッシュ関数であるbcryptが必要です。

.
.
gem 'bcrypt',         '3.1.12'
.
.

bundle installを忘れずに行いましょう。

$ bundle install

②ユーザー登録機能の実装

1.Usersリソースをroutesファイルに追加する

config/routes.rb
Rails.application.routes.draw do
  .
  .
  resources :users
end

2.表示する仮のビューを作成する

show.html.erb
<%= @user.name %>, <%= @user.email %>

3.コントローラーのshowアクションを編集する

app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
  end
end

4.登録フォームを作成する。

app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.email_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>
app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end
end

5.エラーメッセージを表示させる。

問題が生じたためにユーザー登録が行われなかったということをユーザーにわかりやすく伝えるために設定する。

app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

renderメソッドを使ってパーシャルしているので、エラーメッセージに関するファイルを作成します。

# sharedディレクトリを作成する
$ mkdir app/views/shared
app/views/shared/_error_messages.html.erb
<div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

6.createアクションを設定する。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end
config/routes.rb
Rails.application.routes.draw do
  .
  .
  .
  post '/signup',  to: 'users#create'
  resources :users
end
app/views/users/new.html.erb
  .
  .
  .
    # この部分を編集する
    <%= form_for(@user, url: signup_path) do |f| %>
  .
  .
  .

7.フラッシュメッセージを設定する

app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      # この部分を追記
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end
  .
  .
  .
end

flash変数の内容をWebサイトのレイアウトに追加するために以下を編集しましょう。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
      .
      .
      .
      <% flash.each do |message_type, message| %>
        <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
      <% end %>
      .
      .
      .
</html>

参考

基本的な正規表現一覧

ren0826jam
プログラミング歴数ヶ月の初心者ですが、よろしくお願いします。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした