いわりょのBlog

IT関連で学んだことを書いていきます。

rails 基本的なログインシステム part2

すること

ユーザーのログインシステムの続き

  1. 管理人をユーザーモデルで作成
  2. ログインのcontroller作成やルーティング設定
  3. ログインのビュー作成
  4. ログイン機能作成
  5. ちょっと発展機能を加える
  6. ログイン機能を実装したことによって利用できること

前編で3までやったので今回は4のログイン機能作成をやっていきます。
最初にいっておくと「ログインとはなんなのか」、「ログイン機能をどう作るか」を書いていきますが、その機能をどう活用しているかは書いていません。テンプレートでの使用などは最後のパートで書いていこうと思います。

前回の記事↓
ryo10leo.hatenablog.com

そもそもログインするとは

Webアプリケーションは、ステートレスなHTTPプロトコルで通信を行ってリクエストされたページをWebサーバから返しています。特徴としてはたった一度のやりとりで処理が完結するため、Webサーバー側では「状態」を管理することはできません。

どういう状況かをわかりやすくいうと、会話の例えがわかりやすいです。

Aさん:「僕の名前はAです!」
Bさん:「Aさんですね。こんにちは!」
Aさん:「こちらで本を一冊購入させてください!」
Bさん:「え?あなたは誰ですか?」
Aさん:「え...?」

このようにBさんが2回目より前のリクエストを覚えていません。このように、やりとりの状態を継続できないことをステートレスといいます。なのでこのままでは「Aさん」からリクエストがきていることを覚えられない問題があります。

この問題を解決するために、「セッション管理機能」を使います。ユーザーログインの必要なWebアプリケーションでは、セッションと呼ばれる半永続的な接続をコンピュータ間 (ユーザーのパソコンのWebブラウザRailsサーバーなど) に設定します。

そこで方法として使えるのがCookieです。
Webアプリケーションがクライアント側のブラウザを通してクライアントの情報を保持させる機能です。
ということは「ユーザーに情報を持たせる」つまり、リクエストごとにユーザー側のブラウザからCookieを参照すればクライアントの情報を取得でき、接続の状態をあたかも記憶しているような状態が出来上がるわけです。

なので、セッションデータをCookieに保存すればクライアントの情報が保持できます。
これはrailsのsessionメソッドを使えば実現できます。

ここまで理解すると、「ログインする」というのは「クライアント側のブラウザにWebアプリケーションによって、セッション情報をクッキーに保持させることです。

これを踏まえてログイン機能を作っていきます。

ログイン機能作成

まずログインする処理(セッション情報をクッキーに保持させること)はrails側で以下の1行で実現できます。

session['自由な名前'] = 'セッションデータに使う情報'

まあ代入するデータはなんでもいいのですが、ユーザーの情報を保持させたいので今回はユーザーのIDを保持させます。

session[:user_id] = user.id

こうすることでユーザーのID情報をセッションデータとして利用し、クライアント側のブラウザに保存できます。

ここまでの段階で改めて前回の記事から扱っているユーザーのデータモデルを確認します。
f:id:Ryo10Leo:20200105145932p:plain
IDやemail、パスワードなどの情報を持っています。

そして送信にはform_forを使っていました。

app/views/sessons/new.html.erb

  <div id="form">
    <h1>Login</h1>
  
    <%= form_for(:session, url: login_path) do |f| %>

      <div class="form-item">
      <p class="formLabel">Email</p>
      <%= f.email_field :email, class: 'form-style form-control',autocomplete: 'off' %>
      </div>

      <div class="form-item">
      <p class="formLabel">Password</p>
      <%= f.password_field :password, class: 'form-style form-control' %>
      </div>

      <%= f.submit "Log in", class: "login pull-right" %>
    <% end %>
  </div>
</div>

前回説明したように送信されるとコントローラーのcreateアクションで処理が行われるんでしたね。

sessions_controller.rb

class SessionsController < ApplicationController
  def new
  end

  def create
    if 入力された情報のユーザーが存在するか?
        #ログイン処理を書きます
    else
      flash.now[:danger] = '有効なメールアドレス、またはパスワードではありません。'
      render 'new'
    end
  end

  def destroy
  end
end

これを踏まえるとrailsの基本がわかればユーザーがログインするまでの流れを説明すると、なんとなくやることもわかってくるようなこないような

  1. ログイン画面で、emailとパスワードを入力して送信
  2. 送信された情報を元にユーザーの有無を確認して取得
  3. ユーザーが存在すれが、ログインさせる

1では、ログイン画面からデータを送信してログイン処理用のコントローラー(sessionコントローラ)に入力情報を送るんだろな
2では、emailとかパスワードでユーザーを検索して、データベースに存在するユーザーをゲットするんかな
3では、session[:user_id] = user.idでゲットしたユーザーの情報(ここではID)をセッションデータとして保持してさせて、ログイン状態になるんだな

と少しやるべきことが具体的になったかと思います。

あとはそれをコードで書いていけばログイン機能は完成です。

何をするかというとsessions_controllerのcreateメソッドを書き換えていけば良さそうです。
具体的なコードはこんな感じ。

sessions_controller.rb

class SessionsController < ApplicationController
・
・
  def create
  user = User.find_by(email: params[:session][:email])
    if user && user.authenticate(params[:session][:password])
        session[:user_id] = user.id
    else
      flash.now[:danger] = '有効なメールアドレス、またはパスワードではありません。'
      render 'new'
    end
  end
・
・
end

user = User.find_by(email: params[:session][:email])では、送られてきたemailを使って該当するUserを探しています。ユーザーがなければ変数userにはnilが代入されます。

次にifの条件式です。このコードの意味は、「変数userに探したデータが代入されており、かつ、送信されたパスワードがuserが持つのパスワード情報と一致しているか」を確認しています。

前回Userモデルにhas_secure_passwordメソッドを追加したおかげで、実はUserが持つパスワード情報が入力されたものと一致しているかを確認できるauthenticateメソッド使えるようになっています。

user.authenticate('入力されたパスワード')

そのおかげで、ログイン画面で入力されたパスワードつまり、params[:session][:password]が探し出したユーザー(変数user)が持つパスワード情報と一致しているかを、user.authenticate(params[:session][:password])で確認しているということです。それがクリアできたらやっとログインする処理に進めるということです。

なので最後は全ての条件をクリアした後の処理に、これまでに何度か説明したようにsession[:user_id] = user.idとすればログインは完了です!

ただ、session[:user_id] = user.idはログインするという意味ですがパッと見だといまいちログインをしているのかわかりづらいのでヘルパーメソッドでわかりやすくしておきます。

app/helpers/sessions/helper.rb

module SessionsHelper

    #userをログイン状態にする
    def log_in(user)
        session[:user_id] = user.id
    end
end

コントローラーではデフォルトではヘルパーメソッドは使えないので、application_controllerに追加しておきます。

sessions_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  #ヘルパーが使えるように下記を追記
  include SessionsHelper

end
sessions_controller.rb

class SessionsController < ApplicationController
・
・
  def create
  user = User.find_by(email: params[:session][:email])
    if user && user.authenticate(params[:session][:password])
      #↓こちらに変更
        log_in(user)
   (ログイン後の処理はお好みで)
    else
      flash.now[:danger] = '有効なメールアドレス、またはパスワードではありません。'
      render 'new'
    end
  end
・
・
end

うん、わかりやすい笑

あとはログアウトもささっと、
ログアウトはもうお分かりのとおり、ログインと逆のことをすればオッケー。
つまり、セッションが保持しているデータを破棄してしまえばいいんです。

それは、下記の1行でできます。

session[:user_id] = nil

session.delete(:user_id)とかでもできるようですが、、、

これをlogout_pathメソッドが呼ばれたときに、destroyアクションでセッションデータを消してログアウト完了です。

sessions_controller.rb
・
・
  def destroy
    session[:user_id] = nil
    (セッション削除後の処理はお好みで)
  end

これもわかりやすくしておくか。

app/helpers/sessions/helper.rb

module SessionsHelper
・
・
    def log_out
        session[:user_id] = nil
    end
end

これでオッケー!

sessions_controller.rb
・
・
  def destroy
    log_out
    (セッション削除後の処理はお好みで)
  end

このログイン機能は、クライアントがブラウザを閉じてしまうとセッションはデータを破棄してしまいます。つまり、勝手にログアウトします。

次の記事は、その問題を解決して永続的にログイン状態を保持できるような機能を書きたいと思います。
ryo10leo.hatenablog.com