本記事ではログイン機能の実装で広く使われているdeviseを導入せずに、ログイン機能を実装する方法を解説いたします。
動作環境
- Ruby 2.5.1
- Ruby on Rails 5.2.4
Userモデルを作る
データ構造は下記で設定します。
属性名 | データ型 | |
---|---|---|
名前 | name | string |
メールアドレス | string | |
パスワード | password_digest | string |
パスワードを表す属性名を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メソッド | アクション名 |
---|---|---|
ログインのフォームを表示する | GET | new |
フォームから送られてきた情報を元にログインを行う | POST | create |
ログアウトを行う | DELETE | destroy |
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についてもっと詳しく知りたいという方はぜひ読んでみてください。↓↓