54
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

シンプル認証gem sorceryを完全入門するで!!

Last updated at Posted at 2016-10-08

sorceryとは

less is moreな認証用gem。
deviseが「generate したらログインスパーン!!よっしゃ認証できんで!!!!」なのに対して
sorceryは「generateしたら、サブモジュールやら追加したるし、処理は自分で書くんやで」
と言ったイメージ。

個人的な感想としては
高機能の認証を手っ取り早く作りたいなら「devise」
少し面倒臭さは感じても、ある程度はロジックを自分で書いていきたい方は「sorcery」
を使用すればいいんじゃないかなと思います。

sorceryできること

  • 永続ログイン
  • セッションタイムアウト
  • パスワードリセット
  • メールによる認証
  • パスワードなしの認証
  • SNSログイン
  • etc....

こんなけ機能あってless is moreなんか(小声)

懸念点

  • deviseの方が使用例は多いので信頼性は高い
  • 開発が現在それほど活発ではない(一応ver0.9.1でrails5に対応している)
  • 情報量がそれほど多くないのでエラーが辛い
  • コードを書く量がdeviseより多い
  • 複数モデルの認証はSTIを使用しないとおそらくできない

公式Github

#導入

gem 'sorcery'
$ bundle install
$ rails g sorcery:install
$ rake db:migrate

上のコマンドで、sorceryで認証されるUserモデルが作成されます。
Userモデルには暗号化パスワードであるcrypted_passwordカラムと、暗号化用データのsaltが用意されています。

schema.rb
  create_table "users", force: :cascade do |t|
    t.string   "email",                           null: false
    t.string   "crypted_password"
    t.string   "salt"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

基本的な導入はここまで
今回はscaffoldも使用したいので

$ rails g scaffold user email:string crypted_password:string salt:string --migration false

これでscaffold,sorceryなUserモデルが完成。

後application.html.erbにnav適当にナビバーを追加しておきましょう。

application.htnl.erb
  <body>
    <%= yield %>
   <div id="nav">
     <% if current_user %>
       <%= link_to "Edit Profile", edit_user_path(current_user.id) %>
       <%= link_to "Logout", :logout, method: :post %>
     <% else %>
       <%= link_to "Register", new_user_path %> |
       <%= link_to "Login", :login %>
     <% end %>
   </div>
   <div>
     <p id="notice"><%= flash[:notice] %></p>
     <p id="alert"><%= flash[:alert] %></p>
   </div>
   <%= yield %>
  </body>

ここから実装に入ります。
最後まで読めば

email,passwordのログイン機能
永続ログイン
メールによるユーザーアクティベーション機能
パスワードリセット機能
SNSログイン機能

が実装された認証ができます。

シンプルなemail、パスワードログインを実装する

1. フォーム、strong paramatersの変更。

デフォルトではcrypted_passwordになっているため変更。

users/_form.html.erb
<div class="field">
   <%= f.label :password %><br />
   <%= f.password_field :password %>
</div>
<div class="field">
   <%= f.label :password_confirmation %><br />
   <%= f.password_field :password_confirmation %>
</div>
users_controller.rb
params.require(:user).permit(:email, :password, :password_confirmation)

2. validationの追加

user.rb
  validates :password, length: {minimum: 6}
  validates :password, confirmation: true
  validates :password_confirmation, presence: true
  validates :email, uniqueness: true

3. セッション用のコントローラー作成

user_sessions_controller.rb
class UserSessionsController < ApplicationController
  def new
    @user = User.new
  end

  def create
    if @user = login(params[:email], params[:password])
      redirect_back_or_to(:users, notice: 'login successful!!!')
    else
      flash.now[:alert] = 'login failed'
      render action: 'new'
    end
  end

  def destroy
    logout
    redirect_to(:users, notice: 'logged out!')
  end
end

login,logoutメソッドについては説明は要らないと思います。

redirect_back_or_toメソッドは、保存されたURLがある場合そのURLに、ない場合は指定されたURLにリダイレクトします。
例えば、User#editページに行く => 認証が必要 => ログインページに飛ぶ
と言った挙動の場合User#editページが保存され、ログイン成功後にUser#editにリダイレクトすることができます。

4. セッション用のviewの追加

セッション用のフォームの追加を行っていきます。

user_sessions/_form.html.erb
<%= form_tag user_sessions_path, :method => :post do %>

  <div class="field">
    <%= label_tag :email %><br />
    <%= text_field_tag :email %>
  </div>

  <div class="field">
    <%= label_tag :password %><br />
    <%= password_field_tag :password %>
  </div>
  
  <div class="field">
    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation %>
  </div>
  
  <div class="actions">
    <%= submit_tag "Login" %>
  </div>

<% end %>
user_sessions/new.html.erb
<h1>Login</h1>

<%= render 'form' %>

<%= link_to 'Back', user_sessions_path %>

5. routesの変更

routes.rb
Rails.application.routes.draw do
  root 'users#index'
  resources :users
  resources :users_sessions

  get 'login' => 'user_sessions#new'
  post 'logout' => 'user_sessions#destroy'

end

6. ログインチェックの追加

ログインしていないユーザーをアクセス制限するために、require_loginメソッドを使用します。
また、デフォルトでは、ログインしていないユーザーが権限のないページにアクセスすると、rootにリダイレクトしてしまうので、not_authenticatedメソッドを定義して、リダイレクト先を変更します。

application_controller.rb
class ApplicationController < ActionController::Base
  before_action :require_login

  protect_from_forgery with: :exception

  private
  def not_authenticated
    redirect_to login_path, alert: "please login first"
  end
end

これでシンプルなemail, passwordログインは完成です。
コード結構書くやーん

永続ログイン

スクリーンショット 2016-10-08 18.13.05.png
上のようなフォームでチェックボックスをクリックすると永続ログインを有効にする機能を作ります。

Sorceryでは使いたい機能をサブモジュールとしてインストールしていき、それを元に自分たちで処理を書いていくという方式を取っています。
今回はremember_meというサブモジュールをインストールしていきます。

$ rails g sorcery:install remember_me --only-submodules

サブモジュールをインストールすると
sorcery.rbのRails.application.config.sorcery.submodulesにシンボルが追加され
対応したマイグレートファイルが作成されます。

sorcery.rb
Rails.application.config.sorcery.submodules = [:remember_me]
hogehogehogehoge_sorcery_remember_me.rb
class SorceryRememberMe < ActiveRecord::Migration
  def change
    add_column :users, :remember_me_token, :string, :default => nil
    add_column :users, :remember_me_token_expires_at, :datetime, :default => nil

    add_index :users, :remember_me_token
  end
end
$ rake db:migrate

フォームの追加

user_session/_form.html.erb
<div>
  <%= check_box_tag :remember, params[:remember], true %>
  <%= label_tag :remember %>
</div>

パラメーターの追加

user_session_controller.rb
if @user = login(params[:email], params[:password], params[:remember])

ログイン維持時間の設定
下の設定ですると2週間

sorcery.rb
Rails.application.config.sorcery.submodules = [:remember_me]

Rails.application.config.sorcery.configure do |config|
  config.user_config do |user|
    user.remember_me_for = 1209600
  end

  config.user_class = "User"
end

これで完成!!!!!
ログイン維持は簡単。

sorceryで追加したい機能があるときは今回のように
サブモジュールの追加 => migrate => 処理をガリガリ
という手順を追っていきます。
自分で実装している感がよいですね😆

メールによるアクティベーション

スクリーンショット 2016-10-08 18.45.26.png
新規登録したらメールが来て、そのリンクをクリックすると、ユーザー認証が完了するというよくある機能を追加していきます。

1. サブモジュールの追加

$ rails g sorcery:install user_activation --only-submodules

サブモジュールカラムが追加されている確認し
user.user_activation_mailer = UserMailerを追加

sorcery.rb
Rails.application.config.sorcery.submodules = [:remember_me, :user_activation]

Rails.application.config.sorcery.configure do |config|
  config.user_config do |user|
    user.remember_me_for = 1209600
    user.user_activation_mailer = UserMailer
  end

  config.user_class = "User"
end
$ rake db:migrate

2. メイラーの追加

$ rails g mailer UserMailer activation_needed_email activation_success_email

アクションの設定。

user_mailer.rb
class UserMailer < ApplicationMailer

  def activation_needed_email(user)
    @user = user
    @url  = "http://localhost:3000/users/#{user.activation_token}/activate"
    mail(:to => user.email,
     :subject => "Welcome to My Awesome Site")
  end

  def activation_success_email
    @user = user
    @url  = "http://localhost:3000/login"
    mail(:to => user.email,
         :subject => "Your account is now activated")
  end
end

メール本文の作成

activation_needed_email.text.erb
Welcome to example.com, <%= @user.email %>
===============================================

You have successfully signed up to example.com,
your username is: <%= @user.email %>

To login to the site, just follow this link: <%= @url %>

Thanks for joining and have a great day!
activation_success_email.text.erb
Congratz, <%= @user.email %>
===============================================

You have successfully activated your example.com account,
your username is: <%= @user.email %>

To login to the site, just follow this link: <%= @url %>

Thanks for joining and have a great day!

3. activateアクションの追加

認証のためのアクション、ルートを定義していきます。
load_from_activation_tokenでアクセストークンの認証を行っています。

user_controller.rb
def activate
  if (@user = User.load_from_activation_token(params[:id]))
    @user.activate!
    redirect_to(login_path, :notice => 'User was successfully activated.')
  else
    not_authenticated
  end
end
routes.rb
resources :users do
  member do
    get :activate
  end
end

4. letter_openerの追加

開発環境でメールをチェクするためのgemを追加します。
必要のない方は飛ばしてください。

gem 'letter_opener'
development.rb
config.action_mailer.default_url_options = { host: 'localhost:3000' }
config.action_mailer.delivery_method = :letter_opener

これで開発環境でメールが送られると、ブラウザの新しいタブが開きメールが表示されるようになります。

これでメール認証は終了です。
かなり簡単に実装できますね。

パスワードリセット

スクリーンショット 2016-10-08 19.11.38.png
パスワードを忘れてしまった場合のためにパスワードリセット機能を作成します。

1.サブモジュールの追加

まずは勿論サブモジュールの追加です。

$ rails g sorcery:install reset_password --migrations
$ rake db:migrate
sorcery.rb
Rails.application.config.sorcery.submodules = [:user_activation, :reset_password]

Rails.application.config.sorcery.configure do |config|
  config.user_config do |user|
    user.user_activation_mailer = UserMailer
    user.reset_password_mailer = UserMailer
  end

  config.user_class = "User"
end

2. パスワードリセットメールの作成

メール本文の作成

reset_password_email.text.erb
Hello, <%= @user.email %>
===============================================

You have requested to reset your password.

To choose a new password, just follow this link: <%= @url %>

Have a great day!

アクション定義

user_mailer.rb
  def reset_password_email(user)
    @user = User.find user.id
    @url = edit_password_reset_url(@user.reset_password_token)
    mail(:to => user.email,  :subject => "Your password has been reset")
  end

3. パスワードリセット用コントローラの作成

password_resets_controller.rb

class PasswordResetsController < ApplicationController
  skip_before_action :require_login

   def create
     @user = User.find_by_email(params[:email])
     if @user
       @user.deliver_reset_password_instructions!
     else
       return  redirect_to(new_password_reset_path, :notice => 'This email is not correct')
     end
     return redirect_to(root_path, :notice => 'Instructions have been sent to your email.')
   end

   def edit
     @token = params[:id]
     @user = User.load_from_reset_password_token(params[:id])

     if @user.blank?
       not_authenticated
       return
     end
   end

   def update
     @token = params[:id]
     @user = User.load_from_reset_password_token(params[:id])

     if @user.blank?
       not_authenticated
       return
     end

     @user.password_confirmation = params[:user][:password_confirmation]
     if @user.change_password!(params[:user][:password])
       redirect_to(root_path, :notice => 'Password was successfully updated.')
     else
       render :action => "edit"
     end
   end
end

createアクション内のdeliver_reset_password_instructions!で設定されたmailer内のreset_password_emailメソッドを呼びだし、メールを送信しています。
load_from_reset_password_tokenメソッドでは、パスワードトークンを元に、ユーザーを探します。

4. viewの追加

それぞれviewを追加していきます。

password_resets/new.html.erb
<%= form_tag password_resets_path, :method => :post do %>
  <div class="field">
    <%= label_tag :email %><br />
    <%= text_field_tag :email %> <%= submit_tag "Reset my password!" %>
  </div>
<% end %>
users/new.html.erb
<h1>Forgot Password?</h1>
<%= render 'forgot_password_form' %>
password_resets/edit.html.erb
<h1>Choose a new password</h1>
<%= form_for @user, :url => password_reset_path(@token), :html => {:method => :put} do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :email %><br />
    <%= @user.email %>
  </div>
  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password %>
  </div>
  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

これでパスワードリセット完成!!!!!!!

SNSログイン

執筆中

54
59
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?