しおブログ言うてますけど

技術ブログ言うてますけど技術以外のことも発信すると思われ。RubyとかRuby on Railsとか生き方とかジャンルにこだわらず自分の好きなように。初心者なのでお手柔らかに。

ログイン機能の実装(digestを保存するまで)①

Rsilsチュートリアルや現場Railsでも解説されているログイン機能の実装は理解が追いつかなく、なにがなんだかわからない状態だったので、現場Railsを参考にログイン機能の実装をアウトプットしていきたいと思う。
まずWEBアプリケーションでログイン機能を実装する理由は、例えばTODOアプリを作ったとして、やることリストを投稿したとする。自分一人なら、なんら問題は無いが、第三者に記事を投稿されたり削除されてはいけない。
そういった問題を解決するためにログイン機能があり、ログインしたユーザーのみが自分自身のタスクしか扱えないようにするのが目的である。(少々雑かったらすまそん...)
ログイン機能の実装はかなり長くて、1つの記事にしようとすると多分自分でも後で読む気にならないと思うので、いくつかの記事にしてアウトプットする。

前提に必要な知識

Cookie

HTTPはステートレスなプロトコルであるため、WEBブラウザとWEBサーバーの一連のやりとりにおいて、状態を保持し管理する仕組みがない。そのためにショッピングサイトなどで状態を保持し管理する必要がある場合には、Cookieと呼ばれるデータが用いられる。いわば複数のリクエストの間で共有したい「状態」をブラウザ側に保存する仕組みである。

セッション

セッションとは一連の関連性のある処理の流れのことである。例えばショッピングサイトで商品を買う場合の「商品を選ぶ」「商品を買い物かごに入れる」「買い物かごの中身を確認する」「商品を購入する」といった流れがセッションになる。WEBブラウザからに処理を関連性のある一連の処理(=セッション)として扱いたい場合はCookieを用いてセッションを管理できる。

Railsではコントローラーからsessionというメソッドを呼び出すことで、セッションにアクセスできる。sessionはハッシュのように扱うことができる。

 session[:user_id] = @user.id

値を取り出すには下記のように参照する。

 @user_id = session[:user_id]

Railsではセッションの仕組みの一部がCookieによって実現されている。なので直接Cookieを操作することはあまりない。

(ユーザーを表すUser)モデルの作成

ここではアプリケーションを利用するユーザーを表すUserモデルを作成する。Userクラスのデータ構造は下記のように設計する。

意味 属性名 テータ型
名前 name string(文字列)
メールアドレス email string(文字列)
パスワード password_digest string(文字列)

パスワードの属性名がpassword_digestとなっているのは意味があり、これはRailsに標準で付いているhas_secure_passwordという機能を使った命名ルールに沿った属性名である。
has_secure_passwordを使うことによってセキュアにハッシュ化したパスワードを、usersテーブル内のpassword_digestという属性に保存できるようになる。 下記のコマンドでUserモデルを作成する。

$ rails g model User name:string email:string password_digest:string

するとマイグレーションファイルが作成される。
rails db:migrateする前にマイグレーションを編集する。

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.string :email, null: false
      t.string :password_digest, null: false

      t.timestamps
      t.index :email, unique: true
    end
  end
end

名前、メールアドレス、パスワードには必ず文字列が入らないといけないのでNot Null制約をつけている。 編集したらrails db:migrateでデータベースにusersテーブルを作成する。

digestを保存する

上記で説明したhas_secure_passwordを使ってパスワードをセキュアにハッシュ化する。
そのためにbcryptというgemを使う。 Gemfileにコメントアウトされているgem 'bcrypt'のコメントアウトを外してbundle install

gem 'bcrypt', '~> 3.1.7'
$ bundle install

has_secure_passwordの使いかたは簡単でモデルの中にhas_secure_passwordと記述するだけでパスワードをハッシュ化してくれる。

class User < ApplicationRecord
  has_secure_password
end

モデルの中にhas_secure_passwordを記述すると下記2つのデータベースのカラムには対応しない属性が追加される。

  • password
  • password_confirmation

password属性はユーザーが入力したパスワードを一時的に格納するための属性。
password_confirmation属性はpassword属性で入力した値の確認用の属性。
つまりpassword属性の値とpassword_confirmation属性の値が一致してなければ検証に失敗する。
password属性の値とpassword_confirmation属性の値が一致していればpassword_digestにハッシュ化したパスワードの値が保存される。

実際にrails cを使って本当にハッシュ化されるのか検証してみたいと思う。

> user = User.new(name: "テスト", email: "test@test.com", password: "test", password_confirmation: "test")
=> #<User id: nil, name: "テスト", email: "test@test.com", password_digest: "$2a$12$RyxUd9HvQgnA4ur8UXJYpeXRykmYHKKV3ll3nieLMIu...", created_at: nil, updated_at: nil>

password_digestの部分が特定できないハッシュ化されたパスワードになっている。 これをsaveすると、

> user.save
   (0.1ms)  BEGIN
  User Create (0.5ms)  INSERT INTO "users" ("name", "email", "password_digest", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "テスト"], ["email", "test@test.com"], ["password_digest", "$2a$12$RyxUd9HvQgnA4ur8UXJYpeXRykmYHKKV3ll3nieLMIumQw167pkri"], ["created_at", "2021-01-10 07:41:28.956293"], ["updated_at", "2021-01-10 07:41:28.956293"]]
   (4.9ms)  COMMIT
=> true

trueが返ってきて無事に保存されていることがわかる。
ちなみにpassword属性とpassword_confirmation属性の値が一致せずに保存しようとすると、

> test = User.new(name: "テスト", email: "test@test.com", password: "test", password_confirmation: "foobar")
=> #<User id: nil, name: "テスト", email: "test@test.com", password_digest: "$2a$12$U1fzoPdhbC9seU.Ku0kDUeGWSLzxSm5773n/t1KecCy...", created_at: nil, updated_at: nil>
> test.save
   (0.2ms)  BEGIN
   (0.2ms)  ROLLBACK
=> false

falseが返ってきて保存されないということがわかる。

ここまでのまとめ

  • ログイン機能を実装する目的はWEBアプリケーションにて、自分以外のユーザーが他のユーザーの情報を操作できないようにするためである。
  • ユーザー(User)モデルを作成する際、属性には必ず必要なname, email, password_digestとする。
  • password_digestにする理由はRails標準のhas_secure_passwordという機能を使うためである。
  • モデルにhas_secure_passwordと記述すると、passwordpassword_confirmationの2つのデータベースのカラムには対応しない属性が追加される。
  • password属性の値とpassword_confirmation属性の値が一致しているとusersテーブルのpassword_digestカラムにハッシュ化されたパスワードが保存される。
続きます

少し長いので別の記事に続きます。