ユーザー認証

前回終了時のソースコード

Laravelを使ったアプリケーション開発のソースコードは以下のようにしてダウンロードすることができます。

$ git clone https://kiku3.tsbio.info/git/study-laravel.git study_laravel

この章開始時点のソースコードは chapt6 ブランチにあります。

$ git switch chapt6

ソースコードを自分で書いていく場合は、自分用のブランチをつくるとよいです。

$ git switch -c my6 chapt6

Laravelのコードはsrcディレクトリからの相対パスになっています。

認証に必要なパッケージをインストールする

ユーザー認証に使うLaravel Breezeをインストールします。これは特に追加パッケージをインストールするものではなく、ルートなどを設定します。 これをインストールすると、次のファイルが上書きされてしまうので、コピーをつくっておきます。(web.phpはないとエラーがでる)

  • app.css -> lab.css
  • app.js -> lab.css
  • app.blade.php -> lab.blade.php
  • web.php -> oligo.php
# composer require laravel/breeze --dev
# artisan breeze:install blade

breezeに変更されたファイルの修正

resources/views/layouts/app.blade.php を breeze.blade.phpに変更します。ついで、app/View/Components/AppLayout.php の return view('layouts.app');return view('layouts.blade'); に修正します。lab.blade.phpをapp.blade.phpに戻します。

app/View/Components/AppLayout.php
<?php
 
namespace App\View\Components;
 
use Illuminate\View\Component;
use Illuminate\View\View;
 
class AppLayout extends Component
{
    /**
     * Get the view / contents that represents the component.
     */
    public function render(): View
    {
        return view('layouts.breeze'); // layouts/app.blade.phpは自分用に
    }
}

resources/css/app.css
@import 'tailwindcss';
 
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';
 
@theme {
    --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
        'Segoe UI Symbol', 'Noto Color Emoji';
}
 
@import 'lab.css';
@import 'oligo.css';

resources/css/lab.css
.module-tab {
    @apply
        border border-black /* 枠線の設定 */
        first:rounded-l  /* first: によって、最初の要素に限定する。左側(-l)のみ丸める(rounded)。 */
        last:rounded-r  /* 同様に最後の枠線の右側(-r)を丸める。 */
        font-sans font-bold 
        px-6 bg-white  /* 文字の左右の余白(padding x)を6px、背景色を白に設定 */
        hover:bg-blue-500 hover:text-white /* ホバー時の背景色を青、文字色を白に設定 */
    ;
}
 
.feature-tabs {
    @apply flex;
}
.feature-tab {
    @apply border-t border-x border-black rounded-t-md mx-1 px-6 font-sans font-bold;
}
 
.features {
    @apply bg-white p-2 min-h-screen;
}
 
button {
    @apply border-2 py-[0.05em] px-2 rounded shadow-sm shadow-gray-400;
}
button:active {
    @apply shadow-none translate-y-[0.05em] translate-x-[0.05em];
}
 
input[type=checkbox] {
    @apply w-4 h-4;
}

lab.jsはそのままapp.jsに戻す

ユーザーの登録

/registerを使ったユーザーの登録はLaravelからメールを送信できるようにしないといけないので、今回はパス。

代わりに、Model/Userから直接登録する。

# ./artisan tinker
> $user = User::create(['name' => 'test user', 'email' => 'test@example.com', 'password' => 'secret'])
> exit

これで/loginでログインできるようになる。

ログインのしくみ

routes/web.phpを見てみると次のようになっている。

routes/web.php
<?php
 
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
 
Route::get('/', function () {
    return view('welcome');
});
 
Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
 
Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
 
require __DIR__.'/auth.php';

routes/auth.php
<?php
 
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\ConfirmablePasswordController;
use App\Http\Controllers\Auth\EmailVerificationNotificationController;
use App\Http\Controllers\Auth\EmailVerificationPromptController;
use App\Http\Controllers\Auth\NewPasswordController;
use App\Http\Controllers\Auth\PasswordController;
use App\Http\Controllers\Auth\PasswordResetLinkController;
use App\Http\Controllers\Auth\RegisteredUserController;
use App\Http\Controllers\Auth\VerifyEmailController;
use Illuminate\Support\Facades\Route;
 
Route::middleware('guest')->group(function () {
    Route::get('register', [RegisteredUserController::class, 'create'])
        ->name('register');
 
    Route::post('register', [RegisteredUserController::class, 'store']);
 
    Route::get('login', [AuthenticatedSessionController::class, 'create'])
        ->name('login');
 
    Route::post('login', [AuthenticatedSessionController::class, 'store']);
 
    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');
});
 
Route::middleware('auth')->group(function () {
    Route::get('verify-email', EmailVerificationPromptController::class)
        ->name('verification.notice');
 
    Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
        ->middleware(['signed', 'throttle:6,1'])
        ->name('verification.verify');
 
    Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
        ->middleware('throttle:6,1')
        ->name('verification.send');
 
    Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
        ->name('password.confirm');
 
    Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
 
    Route::put('password', [PasswordController::class, 'update'])->name('password.update');
 
    Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
        ->name('logout');
});

/login は routes/auth.php に定義されている。ここで

Route::middleware('guest')->group(function () {
  Route::get('login', [AuthenticatedSessionController::class, 'create'])
    ->name('login');
});
となっている。middleware('guest’)とすることで、/loginに到達できるのはguest (認証されていない)に限られる。

loginの情報は app/Http/Controllers/Auth/AuthenticatedSessionController.php に送られる。この中の store メソッド中で、 LoginRequest $response->authenticate() が認証を行っている。

さらに中身をみていくと src/app/Http/Requests/Auth/LoginRequest.php で、Auth::attempt($this->only('email’, 'password’) という処理があり、ここで認証が行われる。

ヒント: ログインにemailではなくnameを使いたいときは、LoginRequestのrulesでnameを設定し、attemptでそれを渡すようにする。ユーザーにIDを設定したいときは、migration/create_users_table.phpを書き換えて、usersにuidなどのカラムを追加し、それをLoginRequestで受け取るようにする。

認証済みユーザーに限定する

/oligoへのアクセスに認証を必要とするように変更する。

routes/oligo.php
<?php
 
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\OligoController;
 
// オリゴDNAのデータベース
Route::middleware(['auth', 'verified']) // 
    /* authとverifiedというミドルウェアを通過することを要求
     * これらのミドルウェアは vendor/laravel/framework/src/Illuminate/Auth/Middleware/ に存在する。
     * auth -> Authenticate.php
     * verified -> EnsureEmailIsVerified.php
     */
    ->prefix('/oligo')
    ->as('web.oligo.')
    ->group(function (){
        Route::get('/', [OligoController::class, 'index'])->name('index');
        Route::get('/search', [OligoController::class, 'search'])->name('search');
        Route::get('/create', [OligoController::class, 'create'])->name('create');
        Route::post('/', [OligoController::class, 'store'])->name('store');
 
        // 編集フォームとデータ更新
        Route::get('/edit', [OligoController::class, 'edit'])->name('edit');
        Route::put('/{item}', [OligoController::class, 'update'])->name('update');
    });

これをweb.phpで読み込むようにする

routes/web.php
<?php
 
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
 
Route::get('/', function () {
    return view('welcome');
});
 
Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
 
Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
 
require __DIR__.'/auth.php';
require __DIR__.'/oligo.php';

誰が承認されているのかわかるようにヘッダー部分にいれる。ついでにログアウトできるようにする。

resources/views/layouts/app.blade.php
@extends('layouts.html')
 
@section('body')
  <header class='bg-blue-500 text-white py-2 px-2 flex justify-between items-start'>  {{-- 背景色、文字色、余白(padding) --}}
    <h1 class='text-xl leading-none'>{{ config('app.name') }}</h1>
    <div class="flex items-baseline">
      @auth
        <span class='px-2 text-sm'>{{ auth()->user()->name }}</span>
        <form method="POST" action="{{ route('logout') }}" class="inline">
          @csrf
          <button type="submit" class="text-sm text-black bg-gray-200 border-gray-400 px-2 hover:bg-gray-400 hover:text-white">
            {{ __('ログアウト') }}
          </button>
        </form>
      @endauth
      @guest
        ゲスト
      @endguest
    </div>
  </header>
 
  <nav class='bg-yellow-100 py-2 px-2'>
    <ul id='module-tabs' class='flex'>  {{-- タブが横並びになるようにflexを追加 --}}
      <li class='module-tab'><a id='oligo-tab' href='/oligo'>オリゴDNA</a></li>
      {{-- モジュールが複数並んだときの様子がわかるようにダミーを追加 --}}
      <li class='module-tab'><a id='dummy1-tab' href='/dummy1'>ダミー1</a></li>
      <li class='module-tab'><a id='dummy2-tab' href='/dummy2'>ダミー2</a></li>
      <li class='module-tab'><a id='dummy3-tab' href='/dummy3'>ダミー3</a></li>
    </ul>
  </nav>
 
  <main id='modules' class='bg-yellow-100 px-2 min-h-screen'>
    @yield('module')
    
button {
    @apply border-2 py-[0.05em] px-2 rounded shadow-sm shadow-gray-400;
}
button:active {
    @apply shadow-none translate-y-[0.05em] translate-x-[0.05em];
}
 
input[type=checkbox] {
    @apply w-4 h-4;
}
 
  </main>
@endsection

/loginを直接URLに指定したとき、ログイン後に/dashboardへリダイレクトされます。これはapp/Http/Controllers/Auth/AuthenticatedSessionController.phpに設定があり、リダイレクト先を変更できます。

ユーザーの一覧、追加、編集、削除

oligoモジュールをコピーして少しずつ合わせる

必要なもの

  • routes/users.php -> web.phpから読み込む
  • UserController.php UserRequest.php UserService.php
  • views/user
  • css/user.css <- モジュールごとに色を変えるとか