6
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

gem sorceryを使ってログイン機能を実装した話

何をしたか

sorceryというgemを使って、ログイン機能を実装しました。初めて使うgemで、日本語で見られるドキュメントも少なかったので、自分のためにも手順書とマニュアルを作っておこうと思います。

なお、実行環境は下記の通りです。

  • Rails 5.2.3
  • Ruby 2.6.4

どんなgem?

認証のための最小限のロジックを積んだgemで、ユーザーの必要に応じてメソッドを増やしたり拡張をしていくことを目的としています。

▼公式ドキュメントはこちら
Sorcery/sorcery

導入方法

この記事に記す手順は、概ね公式のチュートリアルに沿っています。Gemの導入から、ログイン・ログアウト機能の実装までを記載しています。

以下、手順です。
まずは以下をGemfileに記載し...

Gemfile
gem 'sorcery'

bundle installします。

続けて、以下のコマンドを実行すると

$ rails generate sorcery:install

以下のファイルが生成されます。

create  app/models/user.rb
create  db/migrate/XXXXXXXXX_sorcery_core.rb

また、生成されたマイグレーションファイルの中身はこちらです。

XXXXXXXXX_sorcery_core.rb
class SorceryCore < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :email,            null: false
      t.string :crypted_password
      t.string :salt

      t.timestamps                null: false
    end
    add_index :users, :email, unique: true
  end
end

crypted_password(暗号化されたパスワード)はまだわかるとして、saltカラムが謎ですよね。WEB上にこんな記事を発見しまして、以下、そのGoogle翻訳です。

標準の暗号化では、「ソルト」を使用して、パスワードハッシュの安全性を高めます。 Sorceryでは、パスワードの末尾にランダムな文字列を結合し、その文字列をソルトフィールドに記憶することでこれを行います。

なるほど。。。rails db:migrateをして、無事、導入完了です。

実装(1):ユーザーの作成

View

crypted_passwordや、saltカラムのデータはユーザーが直接変更・表示できないようにするため、ビューには書きません。その代わり、ビューではpasswordpassword_confirmationを利用します。

app/views/users/new.html.slim
= form_with model: @user, local: true do |f|
  = f.label :email
  = f.text_field :email
  = f.label :password
  = f.password_field :password
  = f.label :password_confirmation
  = f.password_field :password_confirmation
  = f.submit "登録"

Controller

また、controllerのstrong_paramaterでは、passwordpassword_confirmationをそれぞれ受け取れるようにします。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  # ...
  private

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

Model

モデルファイルは以下のように記述しました。

app/models/user.rb
class User < ActiveRecord::Base
  authenticates_with_sorcery!

  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  validates :email, uniqueness: true
end

まず、authenticates_with_sorcery!で、Uderモデルにsorceryによる認証機能を持たせます。
passwordpassword_confirmationのようなモデルにないカラムについては、

validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }

こちらの記述で、sorceryに、それがcrypted_passwordカラムの内容であることを認識させます。

余談

余談ですが、new_redord?メソッドは、インスタンスが新規に作成されるものかどうかを判定するActive Recordのメソッドのようですね...:relaxed:(知らなかった&&便利!)

また、if: ->以下は条件付きバリデーションの、シンボルやLambdasてやProcの部分ですね。苦手な部分なので、頑張って復習しないと。。。

コンソールで試す

ここまでできたら、rails cをし、ユーザーが無事作成できることを確かめてみます。

# コンソール
> User.create!(email: "test@email.com", password: "test12345", password_confirmation: "test12345")

> User.last
=> #<User:0x00007fb37b8b4d28
 id: 1,
 email: "test@email.com",
 crypted_password:
  "$2a$10$dOdyPmn65bJ4LKjG5vdL2eU2Hypzpqz5fEgp4LTF8ai0lJtBKL.Q6",
 salt: "QapQbW9crUf1QixW2BD7",
 username: "testuser",
 created_at: Tue, 22 Dec 2020 14:48:37 UTC +00:00,
 updated_at: Tue, 22 Dec 2020 14:48:37 UTC +00:00>

無事、crypted_passwordsaltを持つユーザーが作成されました:relaxed:

追記

そういえば、Usersコントローラーのメソッドを記載していなかったと気がついたので、users_controllerの全体像を記しておきます。

controllers/users_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to login_path
      flash[:notice] = 'ユーザーの作成に成功しました'
    else
      flash.now[:alert] = 'ユーザーの作成に失敗しました'
      render :new
    end
  end

  private

  def user_params
    params.require(:user).permit(:username, :email, :password, :password_confirmation)
  end
end

これで、画面からもユーザーを作成できるようになりました^^

実装(2):ログイン/ログアウト機能の作成

それでは、ログイン・ログアウト機能を実装していきたいと思います。sorceryで提供しているメソッドについては、公式のこちらの部分に記述があります。

それによると、loginメソッドは、emailpasswordremenber_me(デフォルト値はfalse)の3つの引数を取ることができると分かります。

login(email, password, remember_me = false)

今回は、remenber_me機能は利用していないので、emailpasswordでログイン機能を実装します。まずはターミナルからコントローラーを生成し...

$ rails g controller UserSessions new create destroy

コントローラーを記載していきます。

app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
  def new
  end

  def create
    @user = login(params[:email], params[:password])

    if @user
      redirect_back_or_to(new_user_path, notice: 'ログインに成功しました')
    else
      flash.now[:alert] = 'ログインに失敗しました'
      render :new
    end
  end

  def destroy
    logout
    redirect_to(login_path, notice: 'ログアウトしました')
  end
end

redirect_back_or_toはsorceryでフレンドリーフォーワーディングを実現する機能だそうで、こちらの記述によると、(url, flash_hash = {})を引数にとるそうです。

変数名 = {}は引数としてハッシュをまるっと受け取るときの書き方。チェリー本のp.165に書いてありました。

logoutメソッドは説明は不要ですね。

Router

ルーティングは以下のように定義しました。

config/routes.rb
Rails.application.routes.draw do

  get 'login', to: 'user_sessions#new'  # 追記
  post 'login', to: 'user_sessions#create'  # 追記
  delete 'logout', to: 'user_sessions#destroy'  # 追記
  root 'users#new'

  resources :users, only: %i(new create)
end

ここも、特に説明はいらないと思います。

View

最後に、Viewは以下のように定義しました。ここも、特に気遣いが必要だったことはないです。

views/layouts/application.html.slim
  body
    = render 'shared/header'
    = yield

下記のlogged_in?sorceryのメソッドで、ビューで使えるものであると公式のドキュメントには書いてありました。

views/shared/_header.html.slim
h1 AppName
nav
  - if logged_in?
    = link_to 'ログアウト', logout_path, method: :delete
  - else
    = link_to 'ログイン', login_path
views/user_sessions/new.html.slim
= form_with url: login_path, method: :post do |f|
  = f.label :email
  = f.text_field :email
  = f.label :password
  = f.password_field :password
  = f.submit 'ログイン'

完成!

以上で、ログイン・ログアウトメソッドが実装できました。
見た目等はこれから整えていきますが、想像以上に簡単でした^^
Image from Gyazo

Image from Gyazo

感想としては、初めて作ったこともあって、特にGem等を使わずに、Railsのデフォルトの機能を使ってシンプルなログインフォームを作ったぐらいの手間な気がしましたが、

deviseの時みたいに実装にクセがある感じもなかったですし、sorceryで実現できるメソッド一覧にはSNS認証などもあるようで、これからも色々試していきたいです^^

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
6
Help us understand the problem. What are the problem?