12
13

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 3 years have passed since last update.

【Laravel7でユーザー認証_4】パスワード変更フォームを作成する

Last updated at Posted at 2020-06-12

はじめに

Laravelでユーザー認証した後、認証パスワードを変更するフォームを作成する手順をまとめます。

※細かいことはいいからソースが見たい→今回作成した新規ファイルはまとめのソース にまとめました。

環境

XAMPP環境でLaravelが使えるように設定してあります。

  • Windows10 Pro 64bit
  • PHP 7.3.18
  • Laravel 7.12.0
  • MariaDB 10.1.32

また、Laravelプロジェクトは以下の手順で、ユーザー認証をユーザー名とパスワードで行えるようにしてあります。

実装手順

コントローラの作成

パスワード変更用のコントローラを作成します。
今回は、Authディレクトリの中に「ChangePasswordController.php」というファイルを追加しました。

$ php artisan make:controller Auth/ChangePasswordController

ルーティングの設定

以下の仕様で、ルーティングを設定します。

  • 通常のアクセス(GET)の場合は、「Auth\ChangePasswordController」コントローラの「showChangePasswordForm」メソッドを実行。
  • パスワードを変更の処理(POST)の場合は、「Auth\ChangePasswordController」コントローラの「changePassword」メソッドを実行。

それぞれのルーティングには、「password.form」、「password.change」という名前を付けました。

/routes/web.php
  Route::get('/home', 'HomeController@index')->name('home');
+ Route::get('/password/change', 'Auth\ChangePasswordController@showChangePasswordForm')->name('password.form');
+ Route::post('/password/change', 'Auth\ChangePasswordController@ChangePassword')->name('password.change');

viewの作成

viewは、auth/passwords の中にある、reset.blade.php を参考にして change.blade.php を作成しました。
formのアクション先は、ルーティングで付けた「password.change」という名前を使います。

resources/views/auth/passwords/change.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Change Password') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('password.change') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="current_password" class="col-md-4 col-form-label text-md-right">{{ __('Current Password') }}</label>

                            <div class="col-md-6">
                                <input id="current_password" type="password" class="form-control @error('current_password') is-invalid @enderror" name="current_password" required autocomplete="new-password">

                                @error('current_password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('New Password') }}</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">

                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm New Password') }}</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Change Password') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

###翻訳ファイルに追加
view内で使った翻訳文字をja.jsonに追加します。

resource/lang/ja.json
      "Password": "パスワード",
      "Confirm Password": "パスワード (確認用)",
+     "Current Password": "現在のパスワード",
+     "New Password": "新しいパスワード",
+     "Confirm New Password": "新しいパスワード(確認用)",
+     "Change Password": "パスワード変更",

コントローラにメソッド追加

ChangePasswordController に、ルーティングで指定したメソッドを追加します。

showChangePasswordForm メソッド内では、作成したview(auth/passwords/change.blade.php)が表示されるように指定します。

changePassword メソッドは、パスワードを変更する処理を入れたいのですが、ひとまず空にしておきます。

app/Http/Controllers/Auth/ChangePasswordController.php
  <?php
  
  namespace App\Http\Controllers\Auth;
  
  use App\Http\Controllers\Controller;
  use Illuminate\Http\Request;
  
  class ChangePasswordController extends Controller
  {
-     //
+     public function showChangePasswordForm()
+      {
+          return view('auth\passwords\change');
+      }
+      
+     public function changePassword()
+     {
+          /* ===ここにパスワード変更の処理=== */
+     }
    }

一旦確認

http://127.0.0.1:8000/password/change にアクセスして、表示ができるかどうか確認します。
エラーが出てしまった場合、php artisan route:listpassword/change のルーティングが正しくできているか、ルーティングで指定したコントローラ名やメソッド名が正しいかどうか、viewファイル内で指定したaction先が合っているかなどをチェックします。

ログインしていない場合はログイン画面を表示させる

現状だと、ログインをしていなくてもパスワード変更フォームが表示されてしまいます。
ログインをしていない場合は、ログイン画面を表示するようにコントローラを変更します。

app/Http/Controllers/Auth/ChangePasswordController.php
  class ChangePasswordController extends Controller
  {
+     public function __construct()
+     {
+         $this->middleware('auth');
+     }

##パスワード変更の処理

バリデーションチェック

FormRequest の設定

パスワード変更のフォームに入力があったときに、以下の内容で入力が正しいかどうかのチェックをするよう、FormRequestを設定します。

  • 現在のパスワード、新しいパスワードを必須項目になっているか
  • 現在のパスワード、新しいパスワードは8桁以上になっているか
  • 新しいパスワードは確認用と同じ値が入っているか
  • 現在のパスワードに入力された値が、登録されているパスワードと同じかどうか

ChangePasswordRequest という FormRequest を作成します。

$ php artisan make:request ChangePasswordRequest

作成された app/Http/Request/ChangePasswordRequest.php について、バリデーションルールを追加します。

app/Http/Request/ChangePasswordRequest.php
  <?php
  
  namespace App\Http\Requests;
  
  use Illuminate\Foundation\Http\FormRequest;
+ use Illuminate\Contracts\Validation\Validator;
+ use Illuminate\Http\Exceptions\HttpResponseException;
+ use Illuminate\Support\Facades\Auth;
+ use Illuminate\Support\Facades\Hash;

  class ChangePasswordRequest extends FormRequest
  {
      /**
       * Determine if the user is authorized to make this request.
       *
       * @return bool
       */
      public function authorize()
      {
-         return false;
+         return true;
      }
  
      /**
       * Get the validation rules that apply to the request.
       *
       * @return array
       */
      public function rules()
      {
          return [
-              //
+             'current_password' => ['required', 'string', 'min:8'],
+             'password' => ['required', 'string', 'min:8', 'confirmed']
          ];
      }
+
+     public function withValidator(Validator $validator) {
+          $validator->after(function ($validator) {
+              $auth = Auth::user();
+              
+              //現在のパスワードと新しいパスワードが合わなければエラー
+              if (!(Hash::check($this->input('current_password'), $auth->password))) {
+                  $validator->errors()->add('current_password', __('The current password is incorrect.'));
+              }
+          });
+      }
  }

コントローラに読み込み

コントローラ側で、作成した ChangePasswordRequest クラスを使うようにchangePassword メソッドを変更します。

app/Http/Controllers/Auth/ChangePasswordController.php
  <?php

  namespace App\Http\Controllers\Auth;

  use App\Http\Controllers\Controller;
- use Illuminate\Http\Request;
+ use App\Http\Requests\ChangePasswordRequest;

  class ChangePasswordController extends Controller
  {
  ===(略)===
-     public function changePassword(ChangePasswordRequest $request)
+     public function changePassword(ChangePasswordRequest $request)
      {
+          //ValidationはChangePasswordRequestで処理
           /* ===ここにパスワード変更の処理=== */
+
+          // パスワード変更処理後、homeにリダイレクト
+          return redirect()->route('home')->with('status', __('Your password has been changed.'));
      }

####翻訳ファイルに追加

resources/lang/ja.json
+     "The current password is incorrect.": "現在のパスワードが違います。",
+     "Your password has been changed.": "パスワードが変更されました。"
  }

パスワード変更の処理

パスワード変更のボタンを押した後の処理を、コントローラに追加します。
changePasswordメソッドについて、以下のように修正します。

app/Http/Controllers/Auth/ChangePasswordController.php
  <?php

  namespace App\Http\Controllers\Auth;

  use App\Http\Controllers\Controller;
  use Illuminate\Http\Request;
+ use Illuminate\Support\Facades\Auth;

  ===(略)===

      public function changePassword(ChangePasswordRequest $request)
      {
           //ValidationはChangePasswordRequestで処理
-          /* ===ここにパスワード変更の処理=== */
+          //パスワード変更処理
+          $user = Auth::user();
+          $user->password = bcrypt($request->get('password'));
+          $user->save();
 
           // パスワード変更処理後、homeにリダイレクト
           return redirect()->route('home')->with('status', __('Your password has been changed.'));
      }

ヘッダからリンクする

ログインした後、右上に表示されるメニューの中に、パスワード変更のリンクを張っておきます。

resource/views/layouts/app.blade.php
                                <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                        @csrf
                                    </form>
+
+                                   <a class="dropdown-item" href="{{ route('password.form') }}">
+                                       {{ __('Change Password') }}
+                                   </a>
                                </div>

最終確認

パスワード変更画面にアクセスして、

  • 現在のパスワードの欄に適当なパスワードを入れたときに「現在のパスワードが違います。」と表示されるか
  • パスワードが8桁未満の場合「パスワードは、8文字以上で指定してください。」と表示されるか
  • 正しく入力すると、「パスワードが変更されました」と表示されてhomeにリダイレクトされるか
  • 変更後のパスワードでログインできるか

など、希望の動作になるか確認します。

他のログイン済みのデバイスをすべてログアウトさせたい場合

パスワード漏洩対策として、パスワードを変更したら他のデバイスからのログインをすべてログアウトさせたい、という場合、app/Http/Kernel.phpAuthenticateSession を有効にします。

app/Http/Kernel.php
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
-           // \Illuminate\Session\Middleware\AuthenticateSession::class,
+           \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

まとめのソース

View(resources/views/auth/passwords/change.blade.php)
resources/views/auth/passwords/change.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Change Password') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('password.change') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="current_password" class="col-md-4 col-form-label text-md-right">{{ __('Current Password') }}</label>

                            <div class="col-md-6">
                                <input id="current_password" type="password" class="form-control @error('current_password') is-invalid @enderror" name="current_password" required autocomplete="new-password">

                                @error('current_password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('New Password') }}</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">

                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm New Password') }}</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Change Password') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
Controller(app/Http/Controllers/Auth/ChangePassswordController.php)
app/Http/Controllers/Auth/ChangePassswordController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\ChangePasswordRequest;
use Illuminate\Support\Facades\Auth;

class ChangePasswordController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function showChangePasswordForm()
    {
        return view('auth\passwords\change');
    }

    public function changePassword(ChangePasswordRequest $request)
    {
        //ValidationはChangePasswordRequestで処理
        //パスワード変更処理
        $user = Auth::user();
        $user->password = bcrypt($request->get('password'));
        $user->save();

        //homeにリダイレクト
        return redirect()->route('home')->with('status', __('Your password has been changed.'));
  }
}
FormRequest(app/Requests/ChangePasswordRequest.php)
app/Requests/ChangePasswordRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;

class ChangePasswordRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'current_password' => ['required', 'string', 'min:8'],
            'password' => ['required', 'string', 'min:8', 'confirmed']
        ];
    }

    public function withValidator(Validator $validator) {
         $validator->after(function ($validator) {
             $auth = Auth::user();
             
             //現在のパスワードと新しいパスワードが合わなければエラー
             if (!(Hash::check($this->input('current_password'), $auth->password))) {
                 $validator->errors()->add('current_password', __('The current password is incorrect.'));
             }
         });
     }
}

参考サイト

12
13
2

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
12
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?