Rails Tutorial 8章 ログイン処理(session)
セッション
HTTPはステートレスなプロトコルであり、リクエスト一つ一つが独立したトランザクションとなっている。
つまり、ユーザーIDとかはHTTPプロトコル内には保存できない。
その代わりに状態を保存しているのがセッション。
セッションを作成する
セッションをRESTfulなリソースとしてモデリングしたい。そのほうが便利。new→create→destroyみたいな。
まず、セッションコントローラを作る。
$ rails g controller Sessions new
リソースベースルーティングは無駄が多いため、今回は個別に指定(名前付きルーティングのみ)
. . get '/login' => 'sessions#new' post '/login' => 'sessions#create' delete '/logout' => 'sessions#destroy'
ちなみに、 rails routesコマンドで今使用しているルーティングを確認できる
$ rails routes Prefix Verb URI Pattern Controller#Action root GET / static_pages#home help GET /help(.:format) static_pages#help . .
ログインフォームの作成
新規作成ではActiveRecordがエラーメッセージを持っていたけど、今回のセッションはActiveRecordではないた、エラーメッセージを自分で作成する必要がある。
(セッションはDBであれこれするものではないため、ActiveRecordに関連づけられたエラーメッセージは使えない)
セッションフォームとユーザー登録ページの最大の違いは、セッションはモデルを持たないこと。
つまり、セッションは、@userのようなインスタンス変数を持たない。
→新しいセッションフォームを作成するときには、form_forヘルパーに追加の独自の情報を渡さなければいけない。
form_for(@user)
Railsは通常、この記述だけで「フォームのactionは/usersというURLへのPOSTである」と判定するが、
セッションの場合は、リソースの名前と対応するURLを具体的に指定しなければならない。
form_for(:session, url: login_path)
これを用いて作成したログインフォームがこれ
app/views/sessions/new.html.erb <% provide(:title, "Log in") %> <h1>Log in</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div>
そして、生成されたHTMLフォーム
<form accept-charset="UTF-8" action="/login" method="post"> <input name="utf8" type="hidden" value="✓" /> <input name="authenticity_token" type="hidden" value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" /> <label for="session_email">Email</label> <input class="form-control" id="session_email" name="session[email]" type="text" /> <label for="session_password">Password</label> <input id="session_password" name="session[password]" type="password" /> <input class="btn btn-primary" name="commit" type="submit" value="Log in" /> </form>
これを詳しく見るに、paramsハッシュに入る値は
params[:session][:email]
params[:session][:password]
になる。
ユーザーの検索と検証
ログイン処理
↓
ログイン失敗
↓
ログイン成功
の順で作成
まず、最小限のcreateアクションを作成
app/controllers/sessions_controller.rb . def new end def create render 'new' end def destroy end
ここで、ログインフォームのデバック情報
--- session: email: 'user@example.com' password: 'foobar' commit: Log in action: create controller: sessions
paramsを見ると
{ session: { password: "foobar", email: "user@example.com" } }
つまり、
params[:session]
に、
{ password: "foobar", email: "user@example.com" }
が含まれていて、
結果として、
params[:session][:email] params[:session][:password]
これにアクセスすることになる。
ネストした(入れ子になった)ハッシュになっている。
つまり、createアクションの中では、ユーザー認証に必要な情報をparamsから簡単に取り出せる。
そこで、
app/controllers/sessions_controller.rb . . def create user = User.find_by(email: params[:session][:email].downcase if user && user.authenticate(params[:session][:password] #ユーザーログイン後にユーザー情報のページにリダイレクトする else #エラーメッセージを表示する render 'new' end end
以下、解説
・if user && ~~~~
&&(論理積)は取得したユーザーが有効かどうかを決定するためのもの。
つまり、
左辺:ユーザーがDBに存在する
右辺:DBのユーザーとparams値の認証に成功する
この場合のみ、if が true になる。
フラッシュメッセージを表示する
セッションではActiveRecordを使ってないので、ログインに失敗した時は、エラーメッセージの代わりにフラッシュメッセージを使用する。
app/controllers/sessions_controller.rb def create . . else flash[:danger] = 'Invalid email/password combination' . .
※フラッシュメッセージはWebサイトのレイアウトに表示されるので、flash[:danger]で設定したメッセージは自動的に表示される。(スタイルもBootStrapで整えられている)
しかし、これはrenderで強制的に再レンダリングされているため、メッセージが消えない。
んで、こう修正
app/controllers/sessions_controller.rb def create . . else flash.now[:danger] = 'Invalid email/password combination' . .
flash.nowとすることで、その後リクエストが発生するとメッセージが消滅する。
ログイン
有効な値の送信をフォームで扱う。
そのため、cookieを使った一時セッションでユーザーをログインできるようにする。(ブラウザを閉じると消滅する)
セッションを一から実装するには、大量のコードを書かなければいけない(らしい)
それを一箇所にパッケージ化するのがモジュール。
セッションコントローラを生成した時点で、セッション用ヘルパーモジュールも自動生成されている。しかも、ビューに自動的に読み込まれる。(Applicationコントローラにモジュールを読み込ませれば、どのコントローラにも使える)
Applicationコントローラにモジュールを読み込ませる
app/controllers/application_controller.rb class ApplicationController < ActionController::Base . include SessionsHelper end
設定が完了したので、ユーザーログインのコードへ。
log_inメソッド
railsで事前定義済みのsessionメソッドを使って、単純なログインを行えるようにする。(さっき作ったsessionコントローラとは無関係)
session[:user_id] = user,id
この処理を実行すると、ブラウザ内の一時cookieに暗号化済みのユーザーIDが自動で作成される。(ブラウザを閉じると破棄される)
→この後のページで、session[:user_id]を使ってユーザーIDをもとどおりに取り出すことができる。
これを、様々な場所で使えるように、ヘルパーにメソッドを定義
app/helpers/sessions_helper.rb module SessionsHelper #渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end end
sesisonメソッドで作成された一時cookieは自動的に暗号化され、このコードは保護される。
!攻撃者がこの情報をcookieから盗み出しても、それを使って本物のユーザーとしてはログインできない!
→しかし、永続cookieは別
このlog_inメソッドを使い、createアクションに追記。
app/controllers/sessions_controller.rb def create user ~~~~ if ~~~~~ log_in user redirect_to user . .
以下のコード
redirect_to user
を、Railsは自動的に変換して、次のように、プロフィールページへのルーティングにする
user_url(user)