はじめに
前回に続き、以前作成したタスク管理アプリにログイン機能を実装していきます。
今回はその第2弾です。
前回はログイン機能を追加する準備としてユーザー管理機能の基礎部分を実装しました。
しかし今のままでは、管理者以外のユーザーも管理機能を使えてしまいます。ログインしている管理者のみ使えるように制限をかけるために、先にログイン機能を実装します。
ログイン機能の実装
ログイン機能を実装します。ユーザーがログインするためのフォーム画面を表示し、送信されて来た譲歩をもとにユーザーを認証します。ログアウトも提供します。
コントローラの作成
Railsでログイン機能を実装する場合、よくSessionsControllerという名前でコントローラが作られます。SessionsControllerに追加するアクションは以下の通りです。

以下のコマンドを実行してSessionsControllerとnewアクションを作成します。
$ bin/rails g controller Sessions new
route.rbにget ‘sessions/new’が定義されていますので、以下の通りに編集します。
config/route.rb
Rails.application.routes.draw do
get '/login', to: 'sessions#new'
・・・
end
ログインフォームを表示するアクションのURLを/loginに変更できました。
ログインフォームの表示
次に、ログインフォームの画面を作ります。
app/views/sessions/new.html.slimを修正します。
app/views/sessions/new.html.slim
h1 ログイン
= form_with scope: :session, local: true do |f|
.form-group
= f.label :email, 'メールアドレス'
= f.text_field :email, class: 'form-control', id: 'session_email'
.form-group
= f.label :password, 'パスワード'
= f.password_field :password, class: 'form-control', id: 'session_password'
= f.submit 'ログインする', class: 'btn btn-primary'
/loginにアクセスすると以下の画面が表示されます。

ログインフォーム画面を作成できました。
ログインの実行
続いて、ログイン画面から送られる認証情報をもとにログインを実行する部分を作ります。
以下のルーティング設定を追加します。
config/route.rb
Rails.application.routes.draw do
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
・・・
end
次にSessionsController(app/controllers/sessions_controller.rb)にcreateアクションを追加します。送信されたパラメータを安全化するsession_paramsメソッドも追加します。
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: session_params[:email])
if user&.authenticate(session_params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: 'ログインしました。'
else
render :new
end
end
private
def session_params
params.require(:session).permit(:email, :password)
end
end
createアクションでは、メールアドレスでユーザーを検索します。ユーザーが見つかった場合は、送られて来たパスワードによる認証をauthenticateメソッドを使って行います。
authenticateメソッドはUserクラスにhas_secure_passwordを記述した際に自動追加されました。受け取ったパスワードをハッシュ化した結果がUserオブジェクト内部に保存されているdigestと一致するか調べます。
一致している場合認証成功となりUserオブジェクト自身を、一致しない場合認証失敗となりfalseを返します。
認証成功時には、セッションにuser_idを格納します。
これにより以下の2種類を区別できます。
- ・誰もログインしていない状態(session[:user_id]がnil)
- ・誰かがログインしている状態(session[:user_id]にログイン中のユーザーのIDが入っている)
ユーザーがログインしている場合、セッションにユーザーIDが格納された状態になるため、ログイン後はセッションが生きている限り、以下のコードで簡単にユーザーを取得できます。
User.find_by(id: session[:user_id])
このログイン中のユーザーを取得する処理は頻繁に使うことになるので、ApplicationControllerにcurrent_userメソッドを定義して全てのコンローラから呼び出せるようにします。さらにhelper_method指定することで全てのビューからも使えるようにします。
app/controllers/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
これでcurrent_userメソッドで簡単にUserオブジェクトを取得できるようになりました。
ログアウトの実装
現状ではログインしなくてもどの機能でも使えます。各種の機能をログインしている時だけ使えるように制限したいと思います。
その前段階としてログアウト機能を実装します。
ログアウト機能はsession[:user_id]にnilが入っている状態にすることで実現できます。
セッション内のuser_idの情報だけを消すには以下のようにします。
session.delete(:user_id)
しかし、今回はユーザーに紐づく他の情報もセッションに入れているため、ログアウト時にセッション内の全ての情報を削除できるように以下のコードを使用します。
reset_session
ログアウト処理を実行するdestroyアクションをSessionsController(app/controllers/sessions_controller.rb)に追加します。
まずはログアウトのためのルーティングを定義します。
config/route.rb
Rails.application.routes.draw do
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
・・・
end
次にアクションを追加します。
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: session_params[:email])
if user&.authenticate(session_params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: 'ログインしました。'
else
render :new
end
end
def destroy
reset_session
redirect_to root_url, notice: 'ログアウトしました。'
end
private
・・・
end
アクションの準備ができました。最後に画面内にログアウトのリンクを追加します。
app/views/layouts/application.html.slim
doctype html
html
head
title
| Taskapp
meta[name="viewport" content="width=device-width,initial-scale=1"]
= csrf_meta_tags
= csp_meta_tag
= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
body
.app-title.navbar.navbar-expand-md.navbar-light.bg-light
.navbar-brand Taskapp
ul.navbar-nav.ml-auto
- 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'
.container
- if flash.notice.present?
.alert.alert-success= flash.notice
= yield
先ほどヘルパーに定義したcurrent_userメソッドを利用して、ログインしているか否かでリンクの表示を切り替えています。
今回はここまでです。
終わりに
今回はログイン/ログアウト機能を実装しました。しかしこのままではログインしなくてもどの機能でも使えてしまいます。
そこで、次回は認証機能を活かして「ログインしていない場合の機能制限」や「ログインユーザーが閲覧できるデータの制限」などの制限を追加します。
コメント