Laravel Breezeをインストールすると、にログイン、ログアウトなど、認証機能を最小限シンプルに実装することができます。Dockerで Laravelの環境構築できた状態からLaravel Breezeを実装していきます。
前の記事
DockerでLaravelの開発環境を作成する
2023年01月10日
LaravelLaravel Breezeをインストール
composerを使ってBreezeをインストールシた後に、npmも使うので、Dockerfileにコードを追加してコンテナを作り直します。
FROM php:8.0-fpm-buster
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]
ENV COMPOSER_ALLOW_SUPERUSER=1 \
COMPOSER_HOME=/composer
COPY --from=composer:2.0 /usr/bin/composer /usr/bin/composer
RUN apt-get update && \
+ apt-get -y install git unzip libzip-dev libicu-dev libonig-dev nodejs npm && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
docker-php-ext-install intl pdo_mysql zip bcmath
COPY ./php.ini /usr/local/etc/php/php.ini
nodejs と npmを追加しました。コンテナを作り直します。
docker compose down
docker compose up -d --build
Laravelをイントールした、phpコンテナのシェルに入ります。
docker compose exec php bash
Breezeをインストール
phpコンテナのシェルで2つのコマンドを入力します。
composer require laravel/breeze --dev
php artisan breeze:install
これでシンプルなログイン機能が実装できました。これからは見た目部分に必要なコードをインストールしていきます。
npmを更新してコマンドを実行する
npm install -g n
n stable
n
n stable で推奨版のnodejsをインストールして、 n コマンドで使うバージョンを選択します。
バージョンをenterで選択できたら、一度再起動します。
exit
docker compose exec php bash
nodejsがアップデートされたので、npmもアップデートします。
node -v npm -v でバージョン確認することができます。
npm update -g npm
npm install -g npm@8.3.0
npm install
npmのバージョン8.3.0 をインストールするように表示が出たので、インストールします。
npm run dev
Breezeではログイン周りの見た目部分ををtailwindcssとAlpine.jsで作成されているので、それらを npm run dev で読み込んでいます。
これでログイン機能とログイン周りの見た目のデザインが完成できました。
関連記事
Laravel Langで日本語対応にする
2023年01月10日
Laravelログイン機能を確認する
Breezeを実装すると以上の機能が追加されます。
順に見ていきます。
登録
入力フォームからユーザー登録を行います。
register
Route::get('/register', [RegisteredUserController::class, 'create'])
->middleware('guest')
->name('register');
Route::post('/register', [RegisteredUserController::class, 'store'])
->middleware('guest');
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*
* @return \Illuminate\View\View
*/
public function create()
{
return view('auth.register');
}
/**
* Handle an incoming registration request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
event(new Registered($user));
Auth::login($user);
return redirect(RouteServiceProvider::HOME);
}
}
24:User登録ページを表示。
入力情報をPOSTで受け取ると、38:検証、44:ユーザー登録、50:登録後の通知、52:ログイン、54:リダイレクトの処理を順に行っています。
検証に引っかかると$errorsオブジェクトが生成され、直前の場所へ自動的にリダイレクトされます。
詳しくはバリデーションエラー表示
ログイン
登録済みのemailとpasswordを検証してログインします。
login
Route::get('/login', [AuthenticatedSessionController::class, 'create'])
->middleware('guest')
->name('login');
Route::post('/login', [AuthenticatedSessionController::class, 'store'])
->middleware('guest');
Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])
->middleware('auth')
->name('logout');
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*
* @return \Illuminate\View\View
*/
public function create()
{
return view('auth.login');
}
/**
* Handle an incoming authentication request.
*
* @param \App\Http\Requests\Auth\LoginRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(LoginRequest $request)
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(RouteServiceProvider::HOME);
}
/**
* Destroy an authenticated session.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Request $request)
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
20:ログインページを表示。
31: App\Http\Requests\Auth\LoginRequest::authenticate
authenticateメソッドでは、passwordの検証、5回連続で検証に失敗した場合の数十秒のアクセス禁止措置が行われます。
33: 新しいセッションを作成
35:ログインできたので、ダッシュボードにリダイレクトされます。App\Providers\RouteServiceProvider
46:ログアウト
48:セッションを無効
50:CSRFトークンを再生成
52:ルートディレクトリへリダイレクトします。
パスワードリセット
パスワードを忘れた場合などに、メールアドレスを頼りにパスワードを再設定します。
パスワードリセットリンクリクエストフォーム
Route::get('/forgot-password', [PasswordResetLinkController::class, 'create'])
->middleware('guest')
->name('password.request');
Route::post('/forgot-password', [PasswordResetLinkController::class, 'store'])
->middleware('guest')
->name('password.email');
Route::get('/reset-password/{token}', [NewPasswordController::class, 'create'])
->middleware('guest')
->name('password.reset');
Route::post('/reset-password', [NewPasswordController::class, 'store'])
->middleware('guest')
->name('password.update');
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
class PasswordResetLinkController extends Controller
{
/**
* Display the password reset link request view.
*
* @return \Illuminate\View\View
*/
public function create()
{
return view('auth.forgot-password');
}
/**
* Handle an incoming password reset link request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'email' => ['required', 'email'],
]);
// 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)]);
}
}
18:メパスワードリセットリンクリクエストフォームページを表示
31-33:メールアドレスを検証します
38-45:パスワードリセットリンクを作成してユーザーに送信します
メールの内容
パスワードリセットフォーム
メールで本人確認ができたので、新しいパスワードを作成します。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
class NewPasswordController extends Controller
{
/**
* Display the password reset view.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function create(Request $request)
{
return view('auth.reset-password', ['request' => $request]);
}
/**
* Handle an incoming new password request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request)
{
$request->validate([
'token' => ['required'],
'email' => ['required', 'email'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $status == Password::PASSWORD_RESET
? redirect()->route('login')->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}
23:パスワードリセットフォームを表示させます。
34:メールアドレスとパスワードの検証、パスワードのリセット、ログインしてリダイレクトの処理を行います。
パスワードの確認
すでにログインした状態で、再度パスワードを確認したい場合の処理を行います。
Route::get('/confirm-password', [ConfirmablePasswordController::class, 'show'])
->middleware('auth')
->name('password.confirm');
Route::post('/confirm-password', [ConfirmablePasswordController::class, 'store'])
->middleware('auth');
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password view.
*
* @return \Illuminate\View\View
*/
public function show()
{
return view('auth.confirm-password');
}
/**
* Confirm the user's password.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function store(Request $request)
{
if (! Auth::guard('web')->validate([
'email' => $request->user()->email,
'password' => $request->password,
])) {
throw ValidationException::withMessages([
'password' => __('auth.password'),
]);
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended(RouteServiceProvider::HOME);
}
}
20:パスワード確認ページを表示します。
29:再度パスワード確認を行っています。
パスワード確認を適応する
3:パスワード確認の名前ルートを以下のようにミドルウェアに指定することで、遷移する前にパスワード確認をさせることができます。
Route::get('/confirm', function () {
return view('confirm');
})->middleware(['password.confirm']);
メール確認
新しく登録したユーザーへ、確認リンクを含む電子メールが自動的に送信されます。ユーザーが確認メールの確認ボタンを押して初めて確認が必要なページに入ることができます。
コードを追加する
Breezeをインストールしただけでは、メール確認機能は実行されません。
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
// class User extends Authenticatable
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Users.php 12:このインターフェイスをモデルへ追加したら、新しく登録したユーザーへ、確認リンクを含む電子メールが自動的に送信されます。
web.php 3:ミドルウェアを付け加えて、ダッシュボード画面に確認済みのユーザーのみが入ることができるように設定します。
Route::get('/verify-email', [EmailVerificationPromptController::class, '__invoke'])
->middleware('auth')
->name('verification.notice');
Route::get('/verify-email/{id}/{hash}', [VerifyEmailController::class, '__invoke'])
->middleware(['auth', 'signed', 'throttle:6,1'])
->name('verification.verify');
Route::post('/email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
->middleware(['auth', 'throttle:6,1'])
->name('verification.send');
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
class EmailVerificationPromptController extends Controller
{
/**
* Display the email verification prompt.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function __invoke(Request $request)
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(RouteServiceProvider::HOME)
: view('auth.verify-email');
}
}
verify-email
19-21:メール確認していない場合に確認を促すページを表示させます。
コードを追加してダッシュボードにミドルウェアverified
を指定したので、ユーザー登録後にダッシュボードへのアクセスが拒否されて、自動的にこちらの確認ページへ遷移されています。
確認メール
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
class VerifyEmailController extends Controller
{
/**
* Mark the authenticated user's email address as verified.
*
* @param \Illuminate\Foundation\Auth\EmailVerificationRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function __invoke(EmailVerificationRequest $request)
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
}
}
18:ユーザーが電子メールで送信された電子メール確認リンクをクリックしたときに生成されるリクエストを処理します。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Http\Request;
class EmailVerificationNotificationController extends Controller
{
/**
* Send a new email verification notification.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(RouteServiceProvider::HOME);
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}
17:ユーザーが確認メールの再送信をリクエストできるルートの処理をします。
以上がBreezeをインストールして追加される認証機能でした。