マネージドクラウドでRails 6のアプリケーションを運用しよう 〜第2回 認証機能を作成する
ごきげんよう、開発チームのtascriptです!
こちらの記事は、「マネージドクラウドでRails 6のアプリケーションを運用しよう」の連載シリーズ第2回です。
前回ご紹介しました記事はご覧いただけましたでしょうか?
まだご覧になられていない方は下記のリンクから過去の記事をご覧いただけますと幸いです!
【記事一覧】
・マネージドクラウドでRails 6のアプリケーションを運用しよう 〜第1回 Rails 6のアプリケーションをデプロイする〜
前回の振り返り
前回は、Rails6のアプリケーションをマネージドクラウドにデプロイするところまでを紹介しました。無事にデプロイは出来ましたでしょうか?今回からはRails6 のアプリケーションを開発しつつ、マネージドクラウドにデプロイしていくまでを紹介していきます!
作るもの
ブログを投稿するアプリケーションを作ります(わーい)!!
・認証機能
・ブログの投稿・管理
・その他
といった機能の開発について今後ご紹介していきます。
今回の目的
今回は認証機能を実装してみましょう!多くのWebアプリケーションで必要となるログイン、ログアウトの機能を実装してWebアプリケーションに安全な認証機能を導入することを目的とします。
開発を始める前に
連載記事を進めていくことで作成できるアプリケーションのソースコードを下記のリポジトリにて公開しています!この連載では、簡単に開発方法を紹介していますが、より詳細な情報が知りたい場合など、補足資料としてご利用いただけますと幸いです。
今回の開発手順は以下のとおりです。
1. ユーザー情報を保存するテーブルの用意
2. アカウント作成機能を実装
3. ログイン・ログアウト機能を実装
準備はよろしいでしょうか?それでは、早速開発を始めていきましょう!
ユーザー情報を保存するテーブルの用意
ユーザー認証のためには、ユーザー情報を保存するためのテーブルを用意する必要があります。今回は簡単にメールアドレスとパスワードによる認証機能を実装します。Usersテーブルを用意し、以下のようなマイグレーションファイルを用意します。
class CreateUsers < ActiveRecord::Migration[6.0]
def up
create_table :users, id: false do |t|
t.string :id, limit: 36, null: false, primary_key: true
t.string :name, null: false
t.string :email, null: false
t.string :password_digest, null: false
t.string :token
t.datetime :created_at, null: false, default: -> { 'CURRENT_TIMESTAMP' }
t.datetime :updated_at, null: false, default: -> { 'CURRENT_TIMESTAMP' }
end
add_index :users, :email, unique: true
add_index :users, :token, unique: true
end
def down
remove_index :users, :email
drop_table :users
end
end
重要なのはemailカラムとpassword_digestカラムです。ちなみにidカラムはauto incrementではなくuuidを使用するために宣言していたり、利用制限のためにtokenカラムを用意していますが、今回は気にしなくても大丈夫です!
マイグレーションファイルの用意ができましたら、下記のコマンドを実行し、マイグレーションを実施してください。
$ bundle exec rails db:migrate
ロールバックしたい場合は、下記のコマンドを実行してください。
$ bundle exec rails db:rollback
これでテーブル作成は完了です!
アカウント作成機能を実装
1. ルーティングの設定
用意するのは、以下のルーティングです。アカウント作成に必要なルーティングと同時に今後必要になるログイン、ログアウト用のルーティングも定義しておきます。
Rails.application.routes.draw do
root 'home#index'
get 'login', to: 'sessions#new' # ログインページ
post 'login', to: 'sessions#create' # ログイン
delete 'logout', to: 'sessions#destroy' # ログアウト
get 'signup', to: 'users#new' # アカウント作成画面
post 'signup', to: 'users#create' # アカウント作成
end
2. Userモデルの用意
Userモデルを用意し、has_secure_passwordを宣言することでUserモデルに対しpassword、password_confirmation属性を設けることができます。password属性により対象のレコードから生のパスワードが参照できるようになります。同時にpassword_digestカラムにはハッシュ化したパスワードが保存されるようになり、安全なデータ操作が可能になります。さらに、インスタンスメソッドとしてauthenticateメソッドもモデルに付与されます。このメソッドの引数としてパスワードを採用すると、対象ユーザーのパスワードと一致するかどうかをbooleanで返すことができます。
class User < ApplicationRecord
has_secure_password
・
・
・
end
今回は追加機能として、サロゲートキーとなるidカラムをuuidで表現したり、カラムに暗号化した値を保存するようにしていますが、ここはお好みで実装してください。詳しい実装は以下のリンクよりご確認いただけますと幸いです!
3. フォームとコントローラーの用意
新規ユーザー作成のためのフォームとUsersコントローラーを用意します。
まずはUsersコントローラを作成します。フォーム表示に必要なnew、データ保存に必要なcreateといった2つのアクションを用意しましょう。データ保存を安全に実施するために、ストロングパラメーターを付与することも忘れずにお願いします!
class UsersController < ApplicationController
before_action :confirm_auth, only: %i[new create]
def new
@user = User.new
end
def create
@user = User.new(user_params)
User.all.each do |user|
next unless user.email == user_params[:email]
redirect_to(controller: :users, action: :new) && return
end
if @user.save
flash[:success] = 'ユーザーの登録が完了しました。'
redirect_to(controller: :home, action: :index) && return
else
flash[:danger] = 'ユーザーの登録に失敗しました。'
render 'new' && return
end
end
def user_params
params.require(:user).permit(:name, :email, :password, :password_digest)
end
end
続いて、フォームです。form helperとしてform_withを使用しています。form_withはform_tagとform_forの機能を統合したヘルパーであり、Rails 5.1以上で使用可能です。下記のコードからも対象となるモデルとリクエスト先を同時に指定できることがわかります。form_withはデフォルトでAjax通信を実施するようになっていますので、local :true の設定をすることでAjax通信を切るようにしておきます。
<h1>アカウント作成</h1>
<div class="form-area">
<%= form_with model: @user, url: signup_path, local: true do |f| %>
<%= f.label :name %>
<%= f.text_field :name, class: "form-text" %>
<%= f.label :email %>
<%= f.email_field :email, class: "form-text" %>
<%= f.label :password %>
<%= f.password_field :password, class: "form-text" %>
<div class="form-submit">
<%= f.submit "作成" %>
</div>
<% end %>
</div>
ログイン・ログアウト機能を実装
1. フォーム、コントローラー、ヘルパーの用意
ログイン用フォーム、Sessionsコントローラ、Sessionsヘルパーを用意します。
今回はログイン情報をCookieに持たせたいので、SessionsヘルパーにCookieの操作を記述します。log_inメソッドでは、Cookieにはユーザー情報の識別子を保存します。Cookieの有効期限は2時間に設定し、値を暗号化するために署名付きCookieにします。下記コードにはログイン制限のためにtokenカラムの値も保存していますが、ここはお好みで実装してください!
current_userメソッドではCookieからユーザー情報を取得し、インスタンス変数の@current_userに保存します。SessionsヘルパーをApplicationControllerにIncludeすることで、ApplicationControllerを継承する全てのコントローラでログイン中のユーザー情報を確認することができます。
logged_in?メソッドではログインの確認、log_outメソッドではCookieの削除を実施します。
module SessionsHelper
def log_in(user)
expires = 2.hours.from_now
cookies.signed[:user_id] = {
value: user.id,
httponly: true,
secure: Rails.env.production?,
expires: expires
}
cookies.signed[:user_token] = {
value: user.token,
httponly: true,
secure: Rails.env.production?,
expires: expires
}
end
def current_user
user = User.find_by(id: cookies.signed[:user_id])
@current_user = user if user&.authed?(cookies.signed[:user_token])
end
def logged_in?
current_user
!@current_user.nil?
end
def log_out
cookies.delete(:user_id)
cookies.delete(:user_token)
@current_user = nil
end
end
続いて、Sessionsコントローラを用意します。createメソッドでログインdestroyメソッドでログアウトを実装しています。前述したSessionsヘルパーに用意したlog_inメソッドとlog_outメソッドを利用しています。params[:session][:password]については後述します。
class SessionsController < ApplicationController
before_action :confirm_auth, only: %i[new create]
def new; end
def create
user = User.all.find { |u| u.email == params[:session][:email] }
if user&.authenticate(params[:session][:password])
log_in user
flash[:success] = 'ログインに成功しました。'
redirect_to(controller: :dashboard, action: :index)
else
flash[:danger] = 'メールアドレスもしくはパスワードに誤りがあります。'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to login_path
end
end
ログイン用フォームです。新規ユーザー作成と同様に、ここでもユーザーform_withを使用していますが、Sessionsコントローラには対応するモデルがないので、scopeを利用してフォームを作成しています。scopeに「session」を付与することで、フォームタグのaction属性に「/sessions」、inputタグのname属性に「sesssion」プレフィックスを付与します。前述したparams[:session][:password]のようにフォームのパラメータを取得していたのはこのためです。
<h1>ログイン</h1>
<div class="form-area">
<%= form_with scope: :session, url: login_path, local: true do |f| %>
<%= f.label :email %>
<%= f.email_field :email, class: "form-text" %>
<%= f.label :password %>
<%= f.password_field :password , class: "form-text"%>
<%= link_to "アカウントを作成する", signup_path, method: "get", class: "signup-link" %>
<div class="form-submit">
<%= f.submit "ログイン" %>
</div>
<% end %>
</div>
2. ApplicationControllerを改修
SessionsヘルパーのIncludeと、ログイン状況の確認用メソッドを用意します。validate_authメソッドではログイン状況を確認し、ログインしていなければ、Cookieを削除しログイン画面にリダイレクト、confirm_authメソッドではログインしている場合にダッシュボードページにリダイレクトします。ダッシュボードページについては次回に詳細を記載しますが、ログイン後にアクセスできるページです。
class ApplicationController < ActionController::Base
include SessionsHelper
private
def validate_auth
unless logged_in?
log_out
redirect_to(controller: :sessions, action: :new)
end
end
def confirm_auth
redirect_to(controller: :dashboard, action: :index) if logged_in?
end
end
重要なのは、validate_authメソッドで、コントローラのうち、ログイン後にのみ使用するアクションを実施する前にログイン状況を確認するために使用します。例えば、以下のようにコントローラに対しbefore_actionを定義することで、アクション実施前にログイン状況を確認し、ログインしていなければ、Cookieを削除しログイン画面にリダイレクトします。
以上で認証機能が完成しました!お疲れ様です!
振り返り
今回はRails 6で作成する認証機能についてご紹介しました。結構情報量が多くて混乱した方もいらっしゃるかもしれませんが、ここまでくれば、あとはメイン機能の開発に集中できるので諦めずにチャレンジしてみてください!
次回は、ブログの投稿機能の実装について紹介しますので、どうぞお楽しみに!それでは、また次回お会いしましょう!