Ruby

【Ruby on Rails】deviseを使わずにログイン機能を実装する方法

本記事ではログイン機能の実装で広く使われているdeviseを導入せずに、ログイン機能を実装する方法を解説いたします。

動作環境

  • Ruby 2.5.1
  • Ruby on Rails 5.2.4

Userモデルを作る

データ構造は下記で設定します。

属性名データ型
名前namestring
メールアドレスemailstring
パスワードpassword_digeststring

パスワードを表す属性名をpasswordではなくpassword_digestにしている理由は、railsに標準で付いているhas_secure_passwordという機能を使った時の命名ルールとなります。
digestとは、元の値に戻すことのできない一方的な変換を行なった文字列のことです。データベースには直接パスワードは保存されずにdigestが保存されます。digestから実際のパスワードには逆変換できないため、パスワードの漏洩から守ることができます。

今回はrailsに標準で付いているhas_secure_passwordを使いログイン機能を実装していきます。

Userモデルを作成↓↓

$ bin/rails g model user name:string email:string password_digest:string

作成されたマイグレーションファイルを編集する↓↓

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

      t.timestamps
      t.index :email, unique: true
    end
  end
end

書き換えたらマイグレーションを実行します。

パスワードをdigestに変換する仕組みを実装する

パスワードが漏洩しないように、ユーザーがパスワードを入力した際に、digestを生成・保存するようにモデルに実装を加えていきます。

bcryptというgemを使用し、has_secure_passwordが使えるようにします。
railsにはデフォルトでgemfileにコメントアウトされているのでのコメントアウトを外します。

# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'

その後、bundleコマンドを実行する。

Userモデルを下記のように編集する↓↓

class User < ApplicationRecord
  has_secure_password
end

userモデルにhas_secure_passwordを追記すると、データベースのカラムに対応しない属性が2つ追加されます。

  • 1つはpasswordです。これはユーザー入力した生のパスワードを格納するための属性です。
  • 2つ目はpassword_confimaitionです。これは確認用のパスワードを格納するもので、この2つの属性の値が一致して初めて、ユーザー登録に成功したこととします。

ログイン機能の実装

ログイン機能を実装するために、SessionsControllerと言う名前でコントローラーを作成します。
今回SessionsControllerに追加したいアクションは次のようになります。

アクションの内容HTTPメソッドアクション名
ログインのフォームを表示するGETnew
フォームから送られてきた情報を元にログインを行うPOSTcreate
ログアウトを行うDELETEdestroy
Rails.application.routes.draw do
  get '/login', to: 'sessions#new'
  post '/login', to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'

end

ログインを実行する為のcreateアクションを編集します。

class SessionsController < ApplicationController

  def new
  end

  def create
    user=User.find_by(email: session_params[:email])#まず、送られてきたメースアドレスでユーザーを検索する

      if user&.authenticate(session_params[:password])#ユーザーが見つかった場合には、送られてきたパスワードによる認証をauthenticateメソッドを使って行います
      session[:user_id]=user.id#認証に成功した場合に、セッションにuser_idを格納しています。

      redirect_to root_path, notice: 'ログインしました'

    else
      render :new
    end
  end


end

authenticateメソッドはUserクラスにhas_secure_passwordと記述したときに自動的に追加された、認証のためのメソッドです。引数で受け取ったパスワードをハッシュ化して、その結果がUserオブジェクト内に保存されているdigestと一致するか調べます。一致していたら認証成功ということでUserオブジェクト自身を、一致していなければ認証失敗ということでfalseを返します。

以上の記述により、ユーザーがログインしていれば、session[:user_id]にユーザーのIDが格納された状態になるので、ログイン後はセッションがいきている限り、下記のコードでユーザーを簡単に取得することができます。

User.find_by(id: session[:user_id])

これを全てのコントローラーで利用できるように、Application_controller.rbを編集します。

class ApplicationController < ActionController::Base
  helper_method :current_user

  private

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

これで、ログインに成功すればsession[:user_id]にユーザーのidが入り、current_userでuserオブジェクトを取得できる状態になりました。

ログアウト機能を実装する

ログアウト機能とは、ログインしている状態からログインしていない状態に変えることです。したがって、session[:user_id]にnilが入っている状態にすればログアウト成功になります。


class SessionsController < ApplicationController

  ・・・

 def destroy
    reset_session
    redirect_to root_path, notice: 'ログアウトしました。'
  end

 ・・・

end

body
    .app-title.navbar.navbar-expand-md.navbar-light.bg-light
      .navbar-brand Taskleaf

      ul.navbar-nav.ml-auto
         #先ほどヘルパーメソッドに定義したcurrent_userメソッドを利用して、ログインしている時とログアウトしている時で画面の表示を変える
       - if current_user
         li.nav-item= link_to 'タスク一覧',tasks_path, class:'nav-link'
         li.nav-item= link_to 'ユーザー一覧',admin_users_path, class:'nav-link'
         li.nav-item= link_to 'ログアウト',logout_path,method: :delete, class:'nav-link'
       - else
         li.nav-item= link_to 'ログイン',login_path, class:'nav-link'

ユーザーがログインしていない場合に利用制限をかける

ユーザーがログインしている場合にのみ機能を実装したい場合があるかと思います。
その場合には、コントローラーの「フィルタ」という機能を使います。
アクションを処理する前後に、任意の処理を挟むことで、特定の状況の時だけアクションを利用できるように制限するという目的でこの「フィルタ」がよく利用されます。
全機能に共通の処理を施すために、今回はApplicationControllerに記述します。

class ApplicationController < ActionController::Base
  helper_method :current_user
  before_action :login_required#全てのアクションを実行する前に、login_requiredメソッドを実行する

  private

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

  def login_required
    redirect_to login_path unless current_user
  #ユーザーがログインしていない限り、ログイン画面にレダイレクトする
  end
end

今のままだと、ログイン画面を表示するだけでも、無限にレダイレクトされてしまうので、これを避けるために、SessionControllerはログインしていなくても、利用できるようにします。
しかし、SessionControllerの親クラスであるApplicationControllerに定義されているため、skip_before_actionを用いて、定義済みのフィルタを通らないようにします。


class SessionsController < ApplicationController
 skip_before_action :login_required

 ・・・

end

以上です。

参考文献はこちら

Ruby on Rails定番の一冊です。Railsの機能について、プログラミングスクールなどでは教わらなかった内容も学習できて、Railsの理解を深めることができます。
この記事でご紹介した内容に関しても詳細に解説されておりますので、Railsについてもっと詳しく知りたいという方はぜひ読んでみてください。↓↓

  • この記事を書いた人

コウダイ

都内のWeb系自社開発企業に勤務するエンジニア|33歳1児のパパ|ブログ歴4年→月間6,000PV|新卒で手取り18万のホテルマン6年→プログラミングを900時間勉強→100社以上応募しアラサー未経験から7ヶ月でフルリモートのWEB系自社開発エンジニアに転職し年収100万円UP|【人生を自由に、ノンストレスで生きる】をテーマに、30歳で文系・異業種未経験からITエンジニアに転職したノウハウの他、プログラミングやブログで稼ぐ方法など、「時間や場所に縛られずに稼ぐ」方法を発信しています。

-Ruby