Webアプリ開発 ログイン、ログアウト編

# 初めに
webアプリの開発ができたので、開発中に考えたことを書いていきます。
今回はログインについてです。

# 開発環境

macOS Sonoma 14.4.1
PHP 8.3.3
Laravel Installer 5.2.0
Laravel Framework 11.0.5

# ソースコード

https://github.com/opatu07/portfolio

# 考えたこと

## ログイン編

### 作りながら考えたこと

まずはビューで意識したことは
ログイン機能のコンポーネント化です。

フォームの外枠の設定をする
panel.blade.php

フォーム欄の名前をつける
label.blade.php

バリデーションエラーを出す
error.blade.php

フォームでよく使われるものはコンポーネント化をすることで機能修正する際に該当箇所を見つけやすくしました。

次にルートは
```web.php
Route::get('login', [SessionsController::class, 'create'])->middleware('guest')->name('login.view');
Route::post('login', [SessionsController::class, 'store'])->middleware('guest')->name('login.store');
```

ログインはゲストユーザーの時に表示したかったのでget、post両方にmiddlewareをつけました。
get、postの両方に名前をつけることでviewのrootを変更する手間を省きました。

Middlewareの登録は
kernel.phpの編集とmiddlewareファイルの編集をしました。
```kernel.php
    protected $middlewareAliases = [
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    ];
```

```RedirectIfAuthenticated.php
<?php

namespace App\Http\Middleware;

use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;

class RedirectIfAuthenticated
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string ...$guards): Response
    {
        $guards = empty($guards) ? [null] : $guards;

        foreach ($guards as $guard) {
            if (Auth::guard($guard)->check()) {
                return redirect(RouteServiceProvider::HOME);
            }
        }

        return $next($request);
    }
}
```
認証ができたらホーム画面へ移動するようにしました。

コントローラーは
```SessionsController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class SessionsController extends Controller
{
    public function create()
    {   
        return view('auth.login');
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);

        if (! auth()->attempt($validated)) {
            throw ValidationException::withMessages([
                'email || password' => 'メールアドレスとパスワードを再度ご確認ください。'
            ]);
        }

        session()->regenerate();

        return redirect('/')->with('success', 'ログインが完了しました。');
    }

}
```
createメソッドでログイン画面表示します
storeメソッドはログイン処理でバリデーションをして
セッションidを再度作成することでセッションid固定化攻撃を防いでいます

ログインできたらホーム画面にリダイレクトさせています

# レスポンシブ対応
使われるスマホがiPhone8以上の画面サイズなのでiPhone8に対応させました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/b935e88d-fb0a-02cf-d24d-4d0be467a7bf.png" width=40%>


## ログアウト編
### ビュー
```layout.blade.php
<form id="logout-form" method="POST" action="/logout" class="hidden">
    @csrf
</form>
```
ログアウトのビューで意識したことはcsrf攻撃の対応です。
### コントローラ
```SessionsController.php
    public function logout()
    {
        auth()->logout();

        return redirect('/');
    }
```
コントローラではログアウト後にホーム画面へ移動させるようにしました。

### ルート
```web.php
Route::post('logout', [SessionsController::class, 'logout'])->middleware('auth')->name('logout');
```
ルートは名前をつけることでビュー側でルートの更新処理をしなくてもいいようにしました。

### レスポンシブ対応
![res_logout.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/414a7024-daff-ed37-246a-65f3e271409a.gif)


# 今後改善していきたいこと
ビューのコード改善、バリデーションの強化です
ビューのコード改善は
```login.blade.php
    <x-auth-session-status class="mb-4" :status="session('status')" />
```
コードにこれが書いてあってsessionでstatusをauth-session-statusに渡しているのですが必要性がないと感じたのでauth-session-statusのコンポーネントファイルと共に削除したいと思います。

```login.blade.php
<h1 class="text-center font-bold text-xl">ログイン画面</h1> 
```
ログインフォームの名前を決める部分なのですがフォームの項目ごとにラベルをつけるコンポーネントファイルがあるのでそれで名前をつけた方がいいなと考えました。
もしテキストの大きさの関係で出来なそうなら、他のフォームの名前をつけるコンポーネントファイルを作ろうと思います。

```login.blade.php
<input class="border border-gray-200 p-2 w-full rounded"
    name="password"
    id="password"
    type="password"
>
```
ここはフォームの項目で共通部分があるので項目ごとに変数を渡して変更できるようなコンポーネントファイルを作成していきたいと思います。

```login.blade.php
@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
```
ここはパスワードリセットの部分でifディレクティブがいらないかなと思っています。
ルートはweb.phpに書いているのでifなしでルートを記述しようと思います。

バリデーションの強化は
```SessionsController.php
<?php

    public function store(Request $request)
    {
        $validated = $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);
    }

}
```
emailのバリデーションがemailだけだと日本語でメールアドレスを作れる可能性があるのでバリデーションを強化したいと思います。

ログアウトのホーム画面への移動の際にフラッシュを表示させたいと考えました。
withメソッドを使ってメッセージをつけたいと思います。

ログアウトのビューのファイルでactionのルートを変更したいと思います。
web.phpでログアウトのルートに名前をつけたのでそれに変更します。
その方が変更があった際に対応がしやすいからです。

保守をしながら気づいたのですが、ログインの試行回数に上限をつけたいなと考えました。
自分がログインのパスワードを忘れて、何度もログインを試みたのを見て考えました。
この機能をつけるとブルートフォース攻撃対策になるのでやってみようと思います。

# 最後に
アプリを作って自分のコードは完璧なものだと考えていたのですが、全然そんなことはなくてしっかりみてみると点検しないといけない部分は多くあるので他の部分も点検していこうと思います。