もふもふ技術部

IT技術系mofmofメディア

【Rails】deviseを使わない自前のユーザー認証

deviseを使ったユーザー認証はとても便利ですが、カスタマイズをしたいときや、APIモードで使う時には不便なことがあります。
そこで今回は、deviseを使わない自前のユーザー認証を実装します。

下準備

1. Gemfileに使用するGemを追加します

(Gemfileの中でコメントアウトしてあるかと思います。)

gem "bcrypt", "~> 3.1.7"
2. bundle install します
3. Userモデルを作成します
$ rails g model user

20240712000000_create_users.rb

class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :email, null: false
      t.string :password_digest

      t.timestamps
    end
  end
end
4. passwordをハッシュ化する準備をします

app/models/user.rb

class User < ApplicationRecord
  has_secure_password
end

サインアップの実装

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

config/routes.rb

get  '/sign_up', to: 'sign_up#new'
post '/sign_up', to: 'sign_up#create'
get  '/sign_up/thanks', to: 'sign_up#index'
2. コントローラーの実装

app/controllers/sign_up_controller.rb

class SignUpController < ApplicationController
  def index; end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to sign_up_thanks_url, status: :see_other
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.fetch(:user, {}).permit(:email, :password)
  end
end
3. viewsの実装

app/views/sign_up/new.html.erb

<%= form_with model: @user, url: sign_up_path, local: true do |f| %>
  <div>
    <label>メールアドレス</label>
    <%= f.email_field :email, placeholder: "user@example.com", required: true %>
  </div>
  <div>
    <label>パスワード</label>
    <%= f.password_field :password, placeholder: "英数字を2つ以上組み合わせよう", required: true %>
  </div>
  <div>
    <%= f.submit "会員登録する" %>
  </div>
<% end %>

app/views/sign_up/index.html.erb

<div>会員登録ありがとうございます。</div>

ログインの実装

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

config/routes.rb

root 'top#index'
get  '/sign_in', to: 'session#new'
post '/sign_in', to: 'session#create'
2. ヘルパーの実装

app/helper/session_helper.rb

module SessionHelper
  def sign_in(user)
    session[:user_id] = user.id
  end

  def current_user
    if session[:user_id].present?
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

  def signed_in?
    current_user.present?
  end

  def sign_out
    session.delete(:user_id)
    @current_user = nil
  end
end
3. コントローラーの実装

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  include SessionHelper

  before_action :signed_in_user

  private

  def signed_in_user
    unless signed_in?
      redirect_to sign_in_url
    end
  end
end

app/controllers/session_controller.rb

class SessionController < ApplicationController
  skip_before_action :signed_in_user

  def new
    @user = User.new
  end

  def create
    @user = User.find_by(email: user_params[:email].downcase)
    if @user && @user.authenticate(user_params[:password])
      sign_in @user
      redirect_to root_url
    else
      @user = User.new(email: user_params[:email].downcase)
      render 'new', status: :unprocessable_entity
    end
  end

  def destroy
    sign_out if signed_in?
    redirect_to root_url, status: :see_other
  end

  private

  def user_params
    params.fetch(:user, {}).permit(:email, :password)
  end
end

app/controllers/top_controller.rb

class TopController < ApplicationController
  def index; end
end

app/controllers/sign_up_controller.rb
以下を追記する

skip_before_action :signed_in_user
4. viewsの実装

app/views/session/new.html.erb

<%= form_with model: @user, url: sign_in_path, local: true do |f| %>
  <div>
    <label>メールアドレス</label>
    <%= f.text_field :email, placeholder: "user@example.com", required: true %>
  </div>
  <div>
    <label>パスワード</label>
    <%= f.password_field :password, placeholder: "英数字を2つ以上組み合わせよう", required: true %>
  </div>
  <div>
    <%= f.submit "ログイン" %>
  </div>
<% end %>

app/views/top/index.html.erb

<div>ログインしました!</div>

ログアウトの実装

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

config/routes.rb

delete '/sign_out', to: 'session#destroy'
2. コントローラーの修正

app/controllers/session_controller.rb

def destroy
  sign_out if signed_in?
  redirect_to root_url, status: :see_other
end
3. viewsの実装

app/views/top/index.html.erb
ログアウトボタンを追加する

<%= link_to "ログアウト", sign_out_path, data: { 'turbo-method': :delete } %>

終わり

以上で実装は終わりです!
必要に合わせてバリデーションや、カラムを足してみてくださいね。
メールアドレスを使った認証や、パスワードリセットなどは別記事で紹介したいと思います。