webアプリ開発　パスワードリマインダー編

# 初めに
webアプリを開発したので、開発中に考えたことをまとめます
今回は記事の詳細とコメント機能についてです。

# 開発環境
macOS Sonoma 14.4.1
CentoOS Stream X_86_64
Apache/2.4.57
PHP 8.3.6
mysql Ver 8.0.36
phpMyAdmin 5.2.1
composer version 2.7.2
Laravel Installer 5.7.1
Laravel Framework 11.0.5

# ソースコード

https://github.com/opatu07/portfolio

# 考えたこと
## パスワードリマインダー
### 実装事例
パスワードリマインダーはログイン時にパスワードを忘れてしまったユーザーのために作りたいなと思って作りました。

「ログイン画面でパスワードを忘れてしまった方へ」を押してもらうとパスワード再設定用のメール送信画面へ移動します。

![login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/422c9fba-486d-350e-2082-b94f08ca1377.png)

パスワードリセット画面ではメールアドレスを入力してもらうとメールが送信されます。

![password_mail.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/ccfe45b9-3b21-f566-399d-52527211e9df.png)


メール送信がされると送信が確認できます。

![afterPassword_mail.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/a51e3e74-de8f-2f10-0dc0-dfa5e292a426.png)

送信されたメールでは「パスワードリセット」を押すかメール下部にURLが貼ってあるのでそこからパスワードを再設定できます。

![mail.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/c4101688-c6ba-e7af-e4f6-988309de3d9d.png)

パスワード再設定では本人確認のために「現在のメールアドレス」を入力してもらうようにしました。
「新しいパスワード」と「パスワードの確認」を入力してもらってパスワードを再設定することができます。

![passwordReset.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/22021093-f478-65a1-4360-2f581d039ad3.png)

パスワードが更新されるとログイン画面に移動してログインしやすくしました。

![afterPassword.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/7104b3d5-b7cd-4104-ac99-116f988436ed.png)


### ビュー
```login.blade.php
<x-layout>
    <section class="px-6 py-8">
        <main class="max-w-lg mx-auto mt-10">
            <x-panel>
                @if (Route::has('password.request'))
                    <a class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-orange-500 dark:hover:text-gray-100 rounded-md focus:outline-none dark:focus:ring-offset-gray-800" href="{{ route('password.request') }}">
                                {{ __('パスワードを忘れてしまった方へ') }}
                    </a>
                @endif
            </x-panel>
        </main>
    </section>
</x-layout>
```
パスワードリマインダー機能はログインのビューに書きました。
```forgot-password.blade.php
<x-layout>
    <section class="px-6 py-8">
        <main class="w-3/4 mx-auto mt-10">
            <x-panel>
                <h1 class="text-center font-bold text-xl">パスワードリセット画面</h1>

                <div class="mb-4 text-sm py-2 text-gray-600 dark:text-gray-400">
                    {{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
                </div>

                <!-- Session Status -->
                <x-auth-session-status class="mb-4" :status="session('status')" />

                <form method="POST" action="{{ route('password.email') }}">
                    @csrf

                    <!-- Email Address -->
                    <div>
                        <x-form.label name="登録時のメールアドレス"/>  
                        <input class="border border-gray-200 p-2 w-full rounded"
                            name="email"
                            id="email"
                            type="email"
                        >
                        <x-form.error name="email"/>
                    </div>

                    <div class="flex items-center justify-end mt-4">
                        <x-form.button>
                            {{ __('送信！') }}
                        </x-form.button>
                    </div>
                </form>
            </x-panel>
        </main>
    </section>
</x-layout>
```
フォームの画面は大枠をpanelコンポーネントを使って共通化しました。
ラベル、エラーをコンポーネント化しました。

```reset-password.blade.php
<x-layout>
    <section class="px-6 py-8">
        <main class="max-w-lg mx-auto mt-10">
            <x-panel>
                <h1 class="text-center font-bold text-xl">パスワード再設定</h1>

                <form method="POST" action="{{ route('password.store') }}" class="mt-10">
                    @csrf

                    <!-- Password Reset Token -->
                    <input type="hidden" name="token">

                    <!-- Email Address -->
                    <div>
                        <x-form.label name="現在のメールアドレス"/>  
                        <input class="border border-gray-200 p-2 w-full rounded"
                            name="email"
                            id="email"
                        >
                        <x-form.error name="email"/>
                    </div>

                    <!-- Password -->
                    <div class="mt-4">
                        <x-form.label name="新しいパスワード"/>  
                        <input class="border border-gray-200 p-2 w-full rounded"
                            name="new_password"
                            id="new_password"
                            type="password"
                        >
                        <x-form.error name="new_password"/>
                    </div>

                    <!-- Confirm Password -->
                    <div class="mt-4">
                        <x-form.label name="パスワードの確認"/>  
                        <input class="border border-gray-200 p-2 w-full rounded"
                            name="new_password_confirmation"
                            id="new_password_confirmation"
                            type="password"
                        >
                        <x-form.error name="new_password_confirmation"/>
                    </div>

                    <div class="flex items-center justify-end mt-4">
                        <x-form.button>
                            {{ __('パスワードを再設定') }}
                        </x-form.button>
                    </div>
                </form>
            </x-panel>
        </main>
    </section>
</x-layout>
```
パスワード再設定フォームでもpanelコンポーネントを再利用しました。
ラベル、エラー、ボタンのコンポーネントを再利用しました。

### コントローラ
```PasswordResetLinkController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\View\View;

class PasswordResetLinkController extends Controller
{
    /**
     * Display the password reset link request view.
     */
    public function create(): View
    {
        return view('auth.forgot-password');
    }

    /**
     * Handle an incoming password reset link request.
     *
     * @throws \Illuminate\Validation\ValidationException
     */

     public function store(Request $request): RedirectResponse
     {
         $request->validate([
             'email' => 'email|required'
         ]);
 
         // We will send the password reset link to this user. Once we have attempted
         // to send the link, we will examine the response then see the message we
         // need to show to the user. Finally, we'll send out a proper response.
         $status = Password::sendResetLink(
             $request->only('email')
         );
 
         return $status == Password::RESET_LINK_SENT
                     ? back()->with('status', __($status))
                     : back()->withInput($request->only('email'))
                             ->withErrors(['email' => __($status)]);
     }
}
```
```NewPasswordController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\View\View;

class NewPasswordController extends Controller
{
    /**
     * Display the password reset view.
     */
    public function create(Request $request): View
    {
        return view('auth.reset-password',  ['request' => $request]);
    }

    /**
     * Handle an incoming new password request.
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function store(Request $request)
    {
        $request->validate([
            'email' => 'required|email|exists:users',
            'new_password' => 'required|min:6|confirmed',
            'new_password_confirmation' => 'required'
        ]);

        $updatePassword = DB::table('password_reset_tokens')
                            ->where([
                              'email' => $request->email, 
                              'token' => $request->token
                            ]);

        if(!$updatePassword){
            return back()->withInput()->with('error', 'パスワードを更新できませんでした。');
        }

        $user = User::where('email', $request->email)
                    ->update(['password' => Hash::make($request->new_password)]);

        DB::table('password_reset_tokens')->where(['email'=> $request->email])->delete();

        return redirect('/login')->with('success', 'パスワードが更新されました。ログインしてください。');

    }
}
```
### ルート
```web.php
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])->name('password.request');
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])->name('password.email');
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])->name('password.reset');
Route::post('reset-password', [NewPasswordController::class, 'store'])->name('password.store');
```

# 改善点

## reset-password.blade.phpとforgot-password.blade.phpの改善
```
<input class="border border-gray-200 p-2 w-full rounded"
 name="email"
 id="email"
 type="email"
>
```
フォームのinputをコンポーネント化できると思うので変更していこうと思います。


# 終わりに
アプリの機能の改善点を発見できたので、これから改善していきたいと思います！