Rails / Deviceでないユーザー登録・ログインを実装する
Railsでサインアップやログインを実装する時どうしてますか?
だいたいどのアプリ書くときも必要になってくるので、結構つまらない作業だったりする。
個人的には自分でつくるのが好みですが、plataformatec/devise 使ってる人が圧倒的に多い印象。
Deviceていろんな機能がある、けど、結構使わない。
のでもっとシンプルなGemを試してみました。
今回は、下記の機能を実装します。
- サインアップ
- ログイン( + ログインを記憶する)
- パスワードリセット
- ログインに失敗しすぎたらアカウントロック
だいたいこんなもんなんだよね。欲しいのは。
あとはSNS認証とかかな。
インストール
Gemfile
1
gem "sorcery"
bundle install
を忘れずに。
1
$ rails generate sorcery:install remember_me reset_password user_activation brute_force_protection
このコマンドでマイグレーションファイルが作成されるので rake db:migrate
する。
あとであの機能も欲しかった!という場合は下記のように追加する。
1
$ rails generate sorcery:install remember_me --only-submodules
設定ファイル
config/initializers/sorcery.rb
ruby
1
2
3
4
5
6
7
8
9
10
Rails.application.config.sorcery.submodules = [:remember_me, :user_activation, :reset_password, :brute_force_protection]
Rails.application.config.sorcery.configure do |config|
config.user_config do |user|
user.user_activation_mailer = UserMailer
user.reset_password_mailer = UserMailer
user.consecutive_login_retries_amount_limit = 5
end
config.user_class = "User"
end
routes.rb
ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Rails.application.routes.draw do
root 'users#new'
resources :users, except: [:index] do
member do
get :activate
end
end
resources :sessions, only: [:new, :create, :destroy]
resources :password_resets, only: [:create, :edit, :update]
get 'signup' => 'users#new', as: 'signup'
get 'login' => 'sessions#new', as: 'login'
get 'logout' => 'sessions#destroy', as: 'logout'
get 'password_resets/create'
get 'password_resets/edit'
get 'password_resets/update'
end
これは直接関係ないけど、メールの設定。
開発環境のメールテストに Mailtrap.io — Fake smtp testing server. Dummy smtp email testing 使ってます。
config/environments/development.rb
ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
Rails.application.configure do
# Mailtrap
config.action_mailer.default_url_options = { :host => 'dev.phonenumber.jp:3000' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
:user_name => 'xxxxxxx',
:password => 'xxxxxxxx',
:address => 'mailtrap.io',
:domain => 'mailtrap.io',
:port => '2525',
:authentication => :cram_md5
}
end
実装(モデル)
models/user.rb
ruby
1
2
3
4
5
6
7
8
9
class User < ActiveRecord::Base
authenticates_with_sorcery!
validates :email, uniqueness: true
validates :password, length: { minimum: 6 }, if: -> { new_record? || changes["password"] }
validates :password, confirmation: true, if: -> { new_record? || changes["password"] }
validates :password_confirmation, presence: true, if: -> { new_record? || changes["password"] }
end
実装(コントローラー)
controllers/users_controller.rb
ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class UsersController < ApplicationController
skip_before_filter :require_login, only: [:index, :new, :create, :activate]
def new
@user = User.new
end
def create
@user = User.new(strong_params)
if @user.save
redirect_to login_path, notice: "Signed up!"
else
render :new
end
end
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
def show
@user = User.find(params[:id])
end
private
def strong_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
controllers/sessions_controller.rb
ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SessionsController < ApplicationController
def create
user = login(params[:email], params[:password], params[:remember_me])
if user
redirect_back_or_to user_path(user), notice: "Logged in!"
else
invalid_user = User.find_by_email(params[:email])
message = (invalid_user.present? && invalid_user.locked?) ? "This account is locked" : "Email or password was invalid"
flash.now[:alert] = message
render :new
end
end
def destroy
logout
redirect_to login_path, notice: "Logged out!"
end
end
controllers/password_resets_controller.rb
ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class PasswordResetsController < ApplicationController
skip_before_filter :require_login
def create
@user = User.find_by_email(params[:email])
@user.deliver_reset_password_instructions! if @user
# Tell the user instructions have been sent whether or not email was found.
# This is to not leak information to attackers about which emails exist in the system.
redirect_to login_url, 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
# it makes the password confirmation validation work
@user.password_confirmation = params[:user][:password_confirmation]
# it clears the temporary token and updates the password
if @user.change_password!(params[:user][:password])
redirect_to login_path, notice: 'Password was successfully updated.'
else
render :edit
end
end
end
実装(メーラー)
mailers/user_mailer.rb
ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class UserMailer < ActionMailer::Base
default from: 'yourapp.com <[email protected]>', template_path: "mailers/user_mailer"
add_template_helper(ApplicationHelper)
layout 'mailer'
def activation_needed_email(user)
@user = user
@url = "#{root_url}users/#{user.activation_token}/activate"
mail to: user.email,
subject: "Welcome to My Awesome Site"
end
def activation_success_email(user)
@user = user
@url = "#{root_url}login"
mail to: user.email,
subject: "Your account is now activated"
end
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
end
実装(ビュー)
サインアップとログイン
views/users/new.html.haml
haml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
= form_for @user do |f|
= render 'shared/error_messages', object: f.object
.field
= f.label :email
= f.text_field :email
.field
= f.label :password
= f.password_field :password
.field
= f.label :password_confirmation
= f.password_field :password_confirmation
.actions
= f.submit
views/users/show.html.haml
haml
1
2
3
= @user.email
%br
= link_to "log out", logout_path
views/sessions/new.html.haml
haml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
= render 'shared/flash_message'
- if logged_in?
= current_user.email
= form_tag sessions_path do
.field
= label_tag :email
= text_field_tag :email, params[:email]
.field
= label_tag :password
= password_field_tag :password
.field
= check_box_tag :remember_me, 1, params[:remember_me]
= label_tag :remember_me
.actions
= submit_tag "Log in"
%h5
Forgot Password?
= render 'password_resets/forgot_password_form'
パスワードリセット
views/password_resets/edit.html.haml
haml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
%h1
Choose a new password
= form_for @user, url: password_reset_path(@token), html: { method: :put } do |f|
= render 'shared/error_messages', object: f.object
.field
= f.label :email
= @user.email
.field
= f.label :password
= f.password_field :password
.field
= f.label :password_confirmation
= f.password_field :password_confirmation
.actions
= f.submit
views/password_resets/_forgot_password_form.html.haml
haml
1
2
3
4
5
= form_tag password_resets_path, method: :post do
.field
= label_tag :email
= text_field_tag :email
= submit_tag "Reset my password!"
共通部分
views/shared/_error_messages.html.haml
haml
1
2
3
4
5
- if object.errors.present?
.panel.warning
%ul
- object.errors.full_messages.each do |msg|
%li.text-red= msg
views/shared/_flash_message.html.haml
haml
1
2
3
4
- if flash.present?
- flash.each do |key, value|
.panel{class: key}
= value
メーラーのビュー
views/mailers/user_mailer/activation_needed_email.html.haml
haml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%h3
Welcome!
= @user.email
%p
You have successfully signed up to example.com,
your username is:
%br
= @user.email
%p
To login to the site, just follow this link:
%br
= link_to @url, @url
%p
Thanks for joining and have a great day!
views/mailers/user_mailer/activation_success_email.html.haml
haml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Congratz,
= @user.email
%p
You have successfully activated your example.com account,
your username is:
= @user.email
%p
To login to the site, just follow this link:
%br
= link_to @url, @url
%p
Thanks for joining and have a great day!
views/mailers/user_mailer/reset_password_email.html.haml
haml
1
2
3
4
5
6
7
8
9
10
11
Hello,
= @user.email
%p
You have requested to reset your password.
%br
To choose a new password, just follow this link:
%br
= link_to @url, @url
%br
Have a great day!
ふぅ。以上です。
Deviceよりも書くコードは多いかもしれないけど、とても理解しやすいです。
それではー。