はじめに
Laravelでユーザー認証した後、認証パスワードを変更するフォームを作成する手順をまとめます。
※細かいことはいいからソースが見たい→今回作成した新規ファイルはまとめのソース にまとめました。
環境
XAMPP環境でLaravelが使えるように設定してあります。
- Windows10 Pro 64bit
- PHP 7.3.18
- Laravel 7.12.0
- MariaDB 10.1.32
また、Laravelプロジェクトは以下の手順で、ユーザー認証をユーザー名とパスワードで行えるようにしてあります。
- 【Laravel7でユーザー認証_1】基本のき
- 【Laravel7でユーザー認証_2】ユーザー認証を日本語化
- 【Laravel7でユーザー認証_3】ユーザー認証をメールアドレスからユーザー名に変更する
実装手順
コントローラの作成
パスワード変更用のコントローラを作成します。
今回は、Authディレクトリの中に「ChangePasswordController.php」というファイルを追加しました。
$ php artisan make:controller Auth/ChangePasswordController
ルーティングの設定
以下の仕様で、ルーティングを設定します。
- 通常のアクセス(GET)の場合は、「Auth\ChangePasswordController」コントローラの「showChangePasswordForm」メソッドを実行。
- パスワードを変更の処理(POST)の場合は、「Auth\ChangePasswordController」コントローラの「changePassword」メソッドを実行。
それぞれのルーティングには、「password.form」、「password.change」という名前を付けました。
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」という名前を使います。
@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に追加します。
"Password": "パスワード",
"Confirm Password": "パスワード (確認用)",
+ "Current Password": "現在のパスワード",
+ "New Password": "新しいパスワード",
+ "Confirm New Password": "新しいパスワード(確認用)",
+ "Change Password": "パスワード変更",
コントローラにメソッド追加
ChangePasswordController
に、ルーティングで指定したメソッドを追加します。
showChangePasswordForm
メソッド内では、作成したview(auth/passwords/change.blade.php)が表示されるように指定します。
changePassword
メソッドは、パスワードを変更する処理を入れたいのですが、ひとまず空にしておきます。
<?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:list
で password/change
のルーティングが正しくできているか、ルーティングで指定したコントローラ名やメソッド名が正しいかどうか、viewファイル内で指定したaction先が合っているかなどをチェックします。
ログインしていない場合はログイン画面を表示させる
現状だと、ログインをしていなくてもパスワード変更フォームが表示されてしまいます。
ログインをしていない場合は、ログイン画面を表示するようにコントローラを変更します。
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
について、バリデーションルールを追加します。
<?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
メソッドを変更します。
<?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.'));
}
####翻訳ファイルに追加
+ "The current password is incorrect.": "現在のパスワードが違います。",
+ "Your password has been changed.": "パスワードが変更されました。"
}
パスワード変更の処理
パスワード変更のボタンを押した後の処理を、コントローラに追加します。
changePassword
メソッドについて、以下のように修正します。
<?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.'));
}
ヘッダからリンクする
ログインした後、右上に表示されるメニューの中に、パスワード変更のリンクを張っておきます。
<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.php
で AuthenticateSession
を有効にします。
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)
@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)
<?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)
<?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.'));
}
});
}
}