Railsでログイン機能を自作する
はじめに
deviseを使いたくないし、と思ったらsorceryは開発終了してしまってるみたいだし、monbanはきになるけどproductionに載せるには少し不安だ...。
ということで、僕がきいてる限りだと大きな会社のアプリケーションだとかなり自作の事例の方が多いみたいなので、もう自作してしまおうと思いました。その方が勉強にもなりそうだし。
Rails Tutorialでログイン機構の自作部分があったのを思い出したので、そこを参考にします。
http://railstutorial.jp/chapters/log_in_log_out?version=4.2#cha-log_in_log_out
今回のログイン機構では、ブラウザに保存される小さなテキストデータであるcookieを使用してsessionの管理をします。(たいていユーザログイン機能はこの形じゃないかなぁと思います)
Railsのsessionとcookiesメソッドを使っていきます。
大事な方針として、「セッションをRESTfulなリソースとしてモデリングする」という部分。セッションを、他のリソースと同じような形式で、統一的に扱うことができるわけですね。(Rails' Wayに乗せやすいということでもある。)
具体的に説明すると、newで新しいセッションを作成して、そこでログインするとcreateでセッションを保存、ログアウトはdestroyでセッションを破棄する、って感じ。ActiveRecordのデータベースの代わりに、ブラウザのcookie領域がデータの操作先になるというイメージ。なるほど。
つくってみる
Userモデルの作成
Userモデルを作成します。Rails Tutorialの以下の部分を参考に。
http://railstutorial.jp/chapters/modeling_users?version=4.2#sec-user_model
passwordは、Railsのhas_secure_passwordの機能を用いて管理します。
先に、パスワードの管理にハッシュ化を用いるため、Gemfileにbcryptを追加(コメントイン)します。(password_digest
属性を持つモデルクラスを作成しようとすると自動で警告してくれます!すご)
gem 'bcrypt'
そしたら以下のように、has_secure_password機能を用いるための条件であるpassword_digest
属性を持ったモデルクラスを作成します。
$ bin/rails g model User email:string password_digest:string
Userモデルで、has_secure_password
メソッドを呼びます。
class User < ActiveRecord::Base . . . has_secure_password end
モデルクラス内でこのメソッドを読んでおくと、モデルクラスに以下のような機能が追加されます。
セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。 2つのペアの仮想的な属性18 (passwordとpassword_confirmation)が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される。 authenticateメソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalse返すメソッド)。
ログイン機構の作成
viewが必要なのはnewアクションだけなので、以下のようにSessionsController
を作成する。
$ bin/rails g controller Sessions new
以下のようにroutingに加えます。
Rails.application.routes.draw do ... get 'login' => 'sessions#new' post 'login' => 'sessions#create' delete 'logout' => 'sessions#destroy' end
セッションの維持の処理を書きます。
class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) # ユーザログイン後にadminページに遷移する else flash.now[:danger] = 'emailまたはpasswordが間違っています' render "new" end end def destroy end end
SessionsHelperとうモジュールをApplicationControllerにincludeしておきます。
class ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper end
SessionsHelperにlog_in
メソッドを作成します。
module SessionsHelper def log_in(user) session[:user_id] = user.id end end
session[:hoge]
に値を代入すると、ブラウザ内のcookieに暗号化済みの値を自動的に作成してくれます。便利。cookieメソッドの場合は、sessionメソッドと違ってブラウザを閉じた瞬間に消えます。
これで、以下のようにすればloginの機能が一応完成します。
class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user redirect_to admin_path else flash.now[:danger] = 'emailまたはpasswordが間違っています' render "new" end end def destroy end end
その他の便利メソッド
そのほかの、必ず使う便利なヘルパーメソッドもつくっておきます。
current_user
メソッドも作成しておきましょう。
module SessionsHelper ... def current_user @current_user ||= User.find_by(id: session[:user_id]) end end
ここでポイントなのですが、単にUser.find_by...ってUserモデルからsession情報に基づいてuserインスタンスを引っ張ってくるのではなく、@current_userが入ってるかどうかを確認しましょう。Railsでの重要かつ基本的な高速化の方法ですね。
続いてユーザがログインしてるかどうかを真偽値で返してくれるloged_in?
もつくっておきましょう。
module SessionsHelper ... # ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end end
ログアウト機能の実装
log_outメソッドを作成します。sessionを削除するように実装します。@current_user
もnilにしておきます。
module SessionsHelper ... def logout session.delete(:user_id) @current_user = nil end end
destoryメソッドを実装。
class SessionsController < ApplicationController ... def destroy log_out redirect_to root_url end end
次回に続く
ここまでで、ログイン機能の基本的な部分はできました。
ですがこのままだとブラウザを閉じるとセッションの情報が失われてしまいます。
次回の記事で、このセッションを維持する機能を作成していきます。