Laravel Jetstream完全ガイド:導入から実践まで解説する7つの重要ポイント【2024年版】

Laravel Jetstream とは?基礎から理解する特徴と機能

Laravel Jetstreamは、Laravelアプリケーションの認証システムとアプリケーションのスターターキットを提供する公式パッケージです。2020年9月にリリースされて以来、モダンなWebアプリケーション開発に必要な基本機能を効率的に実装できる強力なツールとして広く採用されています。

スターターキットとしてのJetstreamの位置づけ

Jetstreamは単なる認証システムではなく、以下の機能を統合した包括的なスターターキットとして位置づけられています:

  • 認証システム基盤
  • ログイン/登録機能
  • パスワードリセット
  • メール確認機能
  • 二要素認証(2FA)
  • モダンなフロントエンド構成
  • LivewireまたはInertia.jsの選択
  • TailwindCSSによる洗練されたUIコンポーネント
  • Alpine.jsによるインタラクティブな機能
  • チーム管理機能
  • チームの作成と管理
  • ロールベースの権限管理
  • チームメンバーの招待システム

BreezeとFortifyとの徹底比較

LaravelのスターターキットにはJetstream以外にもBreezeとFortifyがありますが、それぞれに特徴があります:

機能JetstreamBreezeFortify
認証基盤
2FA対応
チーム管理
API対応オプション
学習曲線高め低め中程度
カスタマイズ性高い高い非常に高い

主要機能の詳細一覧

  1. 認証機能
  • セッション認証
  • トークンベースのAPI認証
  • 二要素認証(Google Authenticator対応)
  • ブラウザセッション管理
  1. プロファイル管理
  • プロファイル情報の更新
  • プロファイル写真のアップロード
  • パスワード変更
  • 二要素認証の有効化/無効化
  1. チーム機能
  • チームの作成と管理
  • チームメンバーの招待
  • チーム設定の管理
  • ロールとパーミッションの設定
  1. セキュリティ機能
  • CSRF保護
  • XSS対策
  • セッション固定攻撃対策
  • レート制限

Jetstreamは、これらの機能を統合的に提供することで、開発者が本質的なビジネスロジックの実装に集中できる環境を整えています。特に、セキュリティやユーザー管理に関する標準的なベストプラクティスが組み込まれているため、安全で信頼性の高いアプリケーションを素早く構築することができます。

Jetstreamのインストールと初期設定

動作環境の確認とインストール手順

Jetstreamを導入する前に、以下の要件を満たしていることを確認する必要があります:

必要要件:

  • PHP 8.1以上
  • Laravel 10.x以上
  • Node.js 16以上
  • Composerの最新版

インストール手順は以下の通りです:

# 新しいLaravelプロジェクトの作成
composer create-project laravel/laravel example-app

# プロジェクトディレクトリへ移動
cd example-app

# Jetstreamのインストール
composer require laravel/jetstream

# Livewireスタックでのインストール
php artisan jetstream:install livewire

# または、Inertiaスタックでのインストール
php artisan jetstream:install inertia

インストール後の追加手順:

# 依存パッケージのインストール
npm install

# アセットのビルド
npm run build

# データベースマイグレーション
php artisan migrate

Livewire と Inertia の選択基準と特徴

JetstreamではフロントエンドスタックとしてLivewireとInertia.jsの2つの選択肢が用意されています。

Livewireを選ぶべき場合:

  • PHP中心の開発を行いたい場合
  • JavaScriptの知識が限られているチーム
  • シンプルなインタラクションで十分な場合
  • サーバーサイドレンダリングを重視する場合

Inertia.jsを選ぶべき場合:

  • モダンなSPA体験を提供したい場合
  • Vue.jsの知識があるチーム
  • 複雑なフロントエンド機能が必要な場合
  • クライアントサイドのパフォーマンスを重視する場合
機能比較LivewireInertia.js
学習曲線緩やかやや急
PHP依存度高い低い
JS依存度低い高い
開発速度速い中程度
カスタマイズ性中程度高い
SEO対応容易要対策

設定ファイルのカスタマイズポイント

Jetstreamの設定はconfig/jetstream.phpで管理されます。主要な設定項目は以下の通りです:

return [
    // Jetstreamのスタック選択(livewire or inertia)
    'stack' => 'livewire',

    // 有効にする機能の選択
    'features' => [
        Features::profilePhotos(),      // プロフィール写真
        Features::api(),                // API機能
        Features::teams(),              // チーム機能
        Features::accountDeletion(),    // アカウント削除
    ],

    // プロフィール写真のディスク設定
    'profile_photo_disk' => 'public',
];

重要なカスタマイズポイント:

  1. ミドルウェアの設定
   // app/Http/Kernel.php
   protected $middlewareGroups = [
       'web' => [
           // Jetstreamのミドルウェアをカスタマイズ
           \Laravel\Jetstream\Http\Middleware\AuthenticateSession::class,
       ],
   ];
  1. 認証ガード設定
   // config/auth.php
   'guards' => [
       'web' => [
           'driver' => 'session',
           'provider' => 'users',
       ],
       'api' => [
           'driver' => 'sanctum',
           'provider' => 'users',
       ],
   ];
  1. メール設定
   // .env
   MAIL_MAILER=smtp
   MAIL_HOST=mailhog
   MAIL_PORT=1025
   MAIL_USERNAME=null
   MAIL_PASSWORD=null
   MAIL_ENCRYPTION=null
   MAIL_FROM_ADDRESS="hello@example.com"

これらの設定を適切に行うことで、プロジェクトの要件に合わせたJetstreamの環境を構築することができます。設定変更後は必ずキャッシュをクリアすることを推奨します:

php artisan config:clear
php artisan cache:clear
php artisan view:clear

認証機能の実装と拡張方法

二要素認証の設定と実装手順

Jetstreamの二要素認証(2FA)は、Google Authenticatorなどの認証アプリに対応しています。実装手順は以下の通りです:

  1. 2FA機能の有効化
// config/jetstream.php
'features' => [
    Features::twoFactorAuthentication([
        'confirm' => true,      // 確認を必須にする
        'confirmPassword' => true  // パスワード確認を要求
    ]),
],
  1. カスタムバリデーションの追加
// app/Actions/Fortify/TwoFactorAuthenticationController.php
use Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication;

class CustomTwoFactorAuthenticationController extends ConfirmTwoFactorAuthentication
{
    public function __invoke(Request $request)
    {
        // カスタムバリデーション
        $validated = $request->validate([
            'code' => 'required|string|size:6',
        ]);

        // 認証処理
        if (! $request->user()->confirmTwoFactorAuthentication($validated['code'])) {
            throw ValidationException::withMessages([
                'code' => [__('The provided two factor authentication code was invalid.')],
            ]);
        }
    }
}

メール確認機能のカスタマイズ方法

メール確認機能は以下のようにカスタマイズできます:

  1. カスタムメール通知の作成
// app/Notifications/CustomVerifyEmail.php
use Illuminate\Auth\Notifications\VerifyEmail;

class CustomVerifyEmail extends VerifyEmail
{
    public function toMail($notifiable)
    {
        $verificationUrl = $this->verificationUrl($notifiable);

        return (new MailMessage)
            ->subject('メールアドレスの確認')
            ->markdown('emails.verify-email', [
                'url' => $verificationUrl,
                'user' => $notifiable
            ]);
    }
}
  1. 確認メールのテンプレート作成
<!-- resources/views/emails/verify-email.blade.php -->
@component('mail::message')
# メールアドレスの確認

アカウントを有効化するには以下のボタンをクリックしてください。

@component('mail::button', ['url' => $url])
メールアドレスを確認
@endcomponent

@endcomponent
  1. 確認ロジックのカスタマイズ
// app/Models/User.php
use App\Notifications\CustomVerifyEmail;

class User extends Authenticatable implements MustVerifyEmail
{
    public function sendEmailVerificationNotification()
    {
        $this->notify(new CustomVerifyEmail);
    }
}

ソーシャルログインの追加実装

Jetstreamにソーシャルログインを追加する手順は以下の通りです:

  1. Laravel Socialiteのインストール
composer require laravel/socialite
  1. 設定ファイルの追加
// config/services.php
return [
    'github' => [
        'client_id' => env('GITHUB_CLIENT_ID'),
        'client_secret' => env('GITHUB_CLIENT_SECRET'),
        'redirect' => env('GITHUB_CALLBACK_URL'),
    ],
    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect' => env('GOOGLE_CALLBACK_URL'),
    ],
];
  1. ルートの追加
// routes/web.php
use Laravel\Socialite\Facades\Socialite;

Route::get('/auth/{provider}', function ($provider) {
    return Socialite::driver($provider)->redirect();
})->name('social.login');

Route::get('/auth/{provider}/callback', function ($provider) {
    $user = Socialite::driver($provider)->user();

    // ユーザー取得または作成
    $authUser = User::updateOrCreate([
        'email' => $user->email,
    ], [
        'name' => $user->name,
        'password' => Hash::make(Str::random(24)),
    ]);

    Auth::login($authUser);

    return redirect('/dashboard');
})->name('social.callback');
  1. ログインフォームの修正
<!-- resources/views/auth/login.blade.php -->
<div class="social-auth-links text-center mt-4">
    <a href="{{ route('social.login', 'github') }}" class="btn btn-block btn-github">
        <i class="fab fa-github"></i> GitHubでログイン
    </a>
    <a href="{{ route('social.login', 'google') }}" class="btn btn-block btn-google">
        <i class="fab fa-google"></i> Googleでログイン
    </a>
</div>

これらの実装により、セキュアで柔軟な認証システムを構築することができます。実装時は以下の点に注意してください:

  • 二要素認証の有効化は段階的に行う
  • メール確認の再送信制限を適切に設定する
  • ソーシャルログイン時のエラーハンドリングを丁寧に実装する
  • セッション管理とトークンの有効期限を適切に設定する

チーム管理機能の活用術

チーム作成とロール管理の基本設定

Jetstreamのチーム機能は、複数ユーザーで共同作業を行うアプリケーションに最適な機能を提供します。

  1. チーム機能の有効化
// config/jetstream.php
return [
    'features' => [
        Features::teams(['invitations' => true]),
    ],
];
  1. 基本的なロールの定義
// app/Providers/JetstreamServiceProvider.php
use Laravel\Jetstream\Jetstream;

public function boot(): void
{
    $this->configurePermissions();
}

protected function configurePermissions(): void
{
    Jetstream::defaultApiTokenPermissions(['read']);

    Jetstream::role('admin', 'Administrator', [
        'create',
        'read',
        'update',
        'delete',
        'manage-team',
    ]);

    Jetstream::role('editor', 'Editor', [
        'create',
        'read',
        'update',
    ]);

    Jetstream::role('viewer', 'Viewer', [
        'read',
    ]);
}

ポリシーとチームのカスタマイズ方法

チームに関連するポリシーは以下のようにカスタマイズできます:

  1. チームポリシーの拡張
// app/Policies/TeamPolicy.php
class TeamPolicy extends Policy
{
    public function create(User $user): bool
    {
        // プランに基づいてチーム作成を制限
        return $user->subscription?->canCreateTeams() ?? false;
    }

    public function addTeamMember(User $user, Team $team): bool
    {
        // チームメンバー数の制限
        return $team->users()->count() < 10;
    }

    public function updateTeamMember(User $user, Team $team, User $teamMember): bool
    {
        return $user->hasTeamPermission($team, 'manage-team');
    }
}
  1. チームミドルウェアの作成
// app/Http/Middleware/EnsureTeamRole.php
class EnsureTeamRole
{
    public function handle($request, Closure $next, string $role)
    {
        $team = $request->user()->currentTeam;

        if (! $team || ! $request->user()->hasTeamRole($team, $role)) {
            abort(403, '必要な権限がありません。');
        }

        return $next($request);
    }
}

// app/Http/Kernel.php
protected $routeMiddleware = [
    'team.role' => \App\Http\Middleware\EnsureTeamRole::class,
];

チーム招待機能の実装例

  1. カスタム招待メールの作成
// app/Notifications/CustomTeamInvitation.php
class CustomTeamInvitation extends TeamInvitation
{
    public function toMail($notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('チームへの招待')
            ->markdown('mail.team.invitation', [
                'invitation' => $this->invitation,
                'team' => $this->team,
            ]);
    }
}
  1. 招待処理のカスタマイズ
// app/Actions/Jetstream/InviteTeamMember.php
class InviteTeamMember implements InviteTeamMemberContract
{
    public function invite(User $user, Team $team, string $email, string $role = null): void
    {
        // 招待前のバリデーション
        Gate::forUser($user)->authorize('addTeamMember', $team);

        $this->validateMaximumTeamMembers($team);

        // 招待の作成
        $invitation = $team->teamInvitations()->create([
            'email' => $email,
            'role' => $role,
            'expires_at' => now()->addDays(7),
        ]);

        // カスタム通知の送信
        Notification::route('mail', $email)
            ->notify(new CustomTeamInvitation($invitation));
    }

    protected function validateMaximumTeamMembers(Team $team): void
    {
        $count = $team->allUsers()->count();
        $max = config('jetstream.max_team_members', 5);

        if ($count >= $max) {
            throw ValidationException::withMessages([
                'email' => ['チームメンバーの上限に達しています。'],
            ]);
        }
    }
}
  1. 招待受諾処理のカスタマイズ
// routes/web.php
Route::get('/team-invitations/{invitation}', function (TeamInvitation $invitation) {
    if (! auth()->check()) {
        auth()->login($invitation->team->owner);
    }

    if (Gate::check('acceptTeamInvitation', $invitation)) {
        $invitation->team->users()->attach(auth()->id(), ['role' => $invitation->role]);

        $invitation->delete();

        return redirect(config('fortify.home'))->banner(
            '招待を受諾しました。'
        );
    }

    return abort(403);
})->middleware(['signed']);

実装時の重要なポイント:

  1. セキュリティ考慮事項
  • 招待リンクの有効期限設定
  • チームメンバー数の上限設定
  • ロールに基づくアクセス制御
  1. パフォーマンス最適化
  • チーム関連のクエリのキャッシュ
  • N+1問題の回避
  • 非同期処理の活用
  1. UX向上のためのTips
  • 招待状態の可視化
  • わかりやすいエラーメッセージ
  • リアルタイム通知の実装

UIカスタマイズのベストプラクティス

Tailwind CSSを活用したデザイン調整

Jetstreamのデフォルトデザインは優れていますが、多くの場合、企業独自のブランドカラーやデザインガイドラインに合わせた調整が必要となります。

1. カラーパレットのカスタマイズ

// tailwind.config.js
module.exports = {
    theme: {
        extend: {
            colors: {
                brand: {
                    50: '#f2f8ff',
                    100: '#e6f1ff',
                    200: '#bfdaff',
                    300: '#99c3ff',
                    400: '#4d94ff',
                    500: '#0066ff',  // プライマリーカラー
                    600: '#005ce6',
                    700: '#004db3',
                    800: '#003d8c',
                    900: '#003166',
                },
            },
        },
    },
}

実装例:カスタムボタンの作成

<!-- resources/views/components/custom-button.blade.php -->
@props([
    'type' => 'button',
    'variant' => 'primary',
])

@php
$baseClasses = 'inline-flex items-center px-4 py-2 border rounded-md font-semibold text-xs uppercase tracking-widest focus:outline-none focus:ring-2 focus:ring-offset-2 transition ease-in-out duration-150';
$variants = [
    'primary' => 'border-transparent bg-brand-500 text-white hover:bg-brand-600 focus:bg-brand-600 active:bg-brand-600 focus:ring-brand-400',
    'secondary' => 'border-gray-300 bg-white text-gray-700 hover:text-gray-900 hover:bg-gray-50 focus:ring-brand-400',
    'danger' => 'border-transparent bg-red-500 text-white hover:bg-red-600 focus:ring-red-400',
];
@endphp

<button
    type="{{ $type }}"
    {{ $attributes->merge(['class' => $baseClasses . ' ' . $variants[$variant]]) }}
>
    {{ $slot }}
</button>

コンポーネントのオーバーライド方法

Jetstreamのコンポーネントをカスタマイズする際は、段階的なアプローチを取ることが重要です。

1. プロフィールカードのカスタマイズ例

// app/View/Components/CustomProfileCard.php
class CustomProfileCard extends Component
{
    public $user;
    public $team;

    public function __construct($user, $team = null)
    {
        $this->user = $user;
        $this->team = $team;
    }

    public function render()
    {
        return view('components.custom-profile-card', [
            'roles' => $this->team ? $this->user->roles->where('team_id', $this->team->id) : collect(),
            'lastActive' => $this->getLastActiveTime(),
        ]);
    }

    private function getLastActiveTime()
    {
        return Cache::get('user-last-active-' . $this->user->id);
    }
}
<!-- resources/views/components/custom-profile-card.blade.php -->
<div class="bg-white rounded-lg shadow">
    <div class="p-6">
        <div class="flex items-center">
            <div class="flex-shrink-0 h-20 w-20">
                <img class="h-20 w-20 rounded-full object-cover" src="{{ $user->profile_photo_url }}" alt="{{ $user->name }}">
            </div>
            <div class="ml-6">
                <h3 class="text-2xl font-semibold text-gray-900">
                    {{ $user->name }}
                </h3>
                <p class="text-sm text-gray-500">
                    {{ $user->email }}
                </p>
                @if($lastActive)
                    <p class="text-xs text-gray-400 mt-1">
                        最終アクティブ: {{ $lastActive->diffForHumans() }}
                    </p>
                @endif
            </div>
        </div>
        @if($team && $roles->isNotEmpty())
            <div class="mt-4 border-t pt-4">
                <h4 class="text-sm font-medium text-gray-500">チームでの役割</h4>
                <div class="mt-2 flex flex-wrap gap-2">
                    @foreach($roles as $role)
                        <span class="inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium bg-brand-100 text-brand-800">
                            {{ $role->name }}
                        </span>
                    @endforeach
                </div>
            </div>
        @endif
    </div>
</div>

レスポンシブデザインの最適化テクニック

モバイルファーストのアプローチを採用し、段階的に機能を拡張していきます。

1. ダッシュボードレイアウトの最適化

<!-- resources/views/layouts/dashboard.blade.php -->
<div class="min-h-screen bg-gray-100">
    <div class="flex flex-col md:flex-row">
        <!-- サイドナビゲーション(モバイルでは非表示) -->
        <div class="hidden md:flex md:w-64 md:flex-col md:fixed md:inset-y-0">
            <nav class="flex-1 px-2 py-4 bg-white border-r border-gray-200">
                <!-- ナビゲーション項目 -->
            </nav>
        </div>

        <!-- メインコンテンツ -->
        <div class="flex-1 md:ml-64">
            <!-- モバイル用ヘッダー -->
            <div class="sticky top-0 z-10 md:hidden">
                <header class="px-4 py-2 bg-white shadow">
                    <div class="flex items-center justify-between">
                        <button type="button" @click="sidebarOpen = true" class="text-gray-500 hover:text-gray-600">
                            <span class="sr-only">メニューを開く</span>
                            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                            </svg>
                        </button>
                    </div>
                </header>
            </div>

            <!-- コンテンツエリア -->
            <main class="py-6">
                <div class="mx-auto max-w-7xl px-4 sm:px-6 md:px-8">
                    {{ $slot }}
                </div>
            </main>
        </div>
    </div>
</div>

実装のポイント

  1. パフォーマンス最適化
  • コンポーネントの遅延読み込み
  • 画像の最適化
  • CSSの最小化
  1. アクセシビリティ対応
  • 適切な見出し構造
  • フォーカス可能な要素の視覚的フィードバック
  • キーボード操作のサポート
  1. 保守性の向上
  • 共通スタイルの変数化
  • コンポーネントの粒度の適切な設計
  • ドキュメントの整備

これらのカスタマイズにより、Jetstreamのデフォルトデザインを基盤としながら、プロジェクト固有の要件に対応した魅力的なUIを構築することができます。

Jetstreamのセキュリティ対策

セッション管理とブラウザセッションの制御

Jetstreamには高度なセッション管理機能が組み込まれていますが、さらなる強化が可能です。

1. セッション管理の強化

// config/session.php
return [
    'lifetime' => env('SESSION_LIFETIME', 120),     // セッションの有効期限(分)
    'expire_on_close' => true,                      // ブラウザを閉じた時に期限切れ
    'encrypt' => true,                              // セッションの暗号化
    'same_site' => 'lax',                          // SameSite属性の設定
];

2. アクティブセッションの管理実装

// app/Http/Controllers/ActiveSessionController.php
class ActiveSessionController extends Controller
{
    public function index(Request $request)
    {
        return view('security.active-sessions', [
            'sessions' => collect($request->session()->all())
                ->map(function ($value, $key) use ($request) {
                    return [
                        'agent' => $request->header('User-Agent'),
                        'ip' => $request->ip(),
                        'last_active' => now(),
                    ];
                }),
        ]);
    }

    public function destroy(Request $request, $sessionId)
    {
        if ($sessionId !== $request->session()->getId()) {
            // 他のデバイスのセッションを削除
            Session::getHandler()->destroy($sessionId);
        }

        return back(303);
    }
}

API認証管理の実装方法

Sanctumを使用したAPI認証の安全な実装方法を解説します。

1. APIトークン設定のカスタマイズ

// app/Providers/JetstreamServiceProvider.php
use Laravel\Jetstream\Jetstream;

public function boot()
{
    Jetstream::defaultApiTokenPermissions(['read']);

    Jetstream::permissions([
        'read',
        'create',
        'update',
        'delete',
        'admin',
    ]);

    // トークンの有効期限設定
    Sanctum::refreshTokens(function ($token, $user) {
        return $token->created_at->addDays(7);
    });
}

2. セキュアなAPIルートの定義

// routes/api.php
Route::middleware(['auth:sanctum', 'verified'])->group(function () {
    Route::get('/user/profile', function (Request $request) {
        // レート制限の適用
        return Cache::remember('user.'.$request->user()->id.'.profile', 60, function () use ($request) {
            return $request->user()->load('profile');
        });
    })->middleware(['throttle:api']);

    // 権限に基づくルート制限
    Route::middleware(['ability:admin'])->group(function () {
        Route::post('/admin/users', [AdminController::class, 'store']);
    });
});

クロスサイトリクエストフォージェリ対策

CSRF対策を強化する実装例を示します。

1. CSRFミドルウェアのカスタマイズ

// app/Http/Middleware/CustomVerifyCsrfToken.php
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;

class CustomVerifyCsrfToken extends VerifyCsrfToken
{
    protected $except = [
        // CSRF保護から除外するURL
        'stripe/*',
        'webhook/*',
    ];

    protected function tokensMatch($request)
    {
        $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

        if (!$token && $header = $request->header('X-XSRF-TOKEN')) {
            $token = Crypt::decrypt($header, static::serialized());
        }

        // トークンの検証強化
        if (!is_string($token)) {
            return false;
        }

        $sessionToken = $request->session()->token();

        return hash_equals(
            $sessionToken,
            $token
        );
    }
}

2. セキュアなフォーム実装

<!-- resources/views/forms/secure-form.blade.php -->
<form method="POST" action="{{ route('secure.action') }}" 
      x-data="{ submitting: false }"
      @submit.prevent="if(!submitting) { submitting = true; $event.target.submit(); }">
    @csrf

    <!-- ワンタイムトークンの生成 -->
    <input type="hidden" name="one_time_token" value="{{ Str::random(40) }}">

    <!-- 二重送信防止 -->
    <x-button :disabled="submitting">
        <span x-show="!submitting">送信</span>
        <span x-show="submitting">処理中...</span>
    </x-button>
</form>

セキュリティ強化のベストプラクティス

  1. 入力値のバリデーション強化
// app/Http/Requests/SecureFormRequest.php
class SecureFormRequest extends FormRequest
{
    public function rules()
    {
        return [
            'email' => ['required', 'email', 'max:255', 'regex:/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/'],
            'password' => [
                'required',
                'string',
                'min:12',
                'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{12,}$/',
            ],
            'one_time_token' => ['required', 'string', 'size:40'],
        ];
    }

    public function messages()
    {
        return [
            'password.regex' => 'パスワードは大文字、小文字、数字、特殊文字を含む必要があります。',
        ];
    }
}
  1. ログイン試行の制限
// app/Http/Controllers/Auth/CustomLoginController.php
use Illuminate\Cache\RateLimiter;

class CustomLoginController extends Controller
{
    protected function attemptLogin(Request $request)
    {
        $limiter = app(RateLimiter::class);
        $key = 'login.'.$request->ip();

        if ($limiter->tooManyAttempts($key, 5)) {
            $seconds = $limiter->availableIn($key);
            throw ValidationException::withMessages([
                'email' => [__('auth.throttle', ['seconds' => $seconds])],
            ]);
        }

        if (Auth::attempt($request->only('email', 'password'))) {
            $limiter->clear($key);
            return true;
        }

        $limiter->hit($key, 60);
        return false;
    }
}
  1. セキュリティヘッダーの設定
// app/Http/Middleware/SecurityHeaders.php
class SecurityHeaders
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');
        $response->headers->set('X-XSS-Protection', '1; mode=block');
        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $response->headers->set('Content-Security-Policy', "default-src 'self'");
        $response->headers->set('Permissions-Policy', 'geolocation=()');

        return $response;
    }
}

これらの実装により、Jetstreamアプリケーションのセキュリティを大幅に強化することができます。ただし、セキュリティは継続的な取り組みが必要な分野であり、定期的な見直しと更新が重要です。

実際の運用での注意点とトラブル対処

パフォーマンス最適化のベストプラクティス

Jetstreamアプリケーションのパフォーマンスを最適化するための主要なポイントを解説します。

1. データベースクエリの最適化

// app/Models/Team.php
class Team extends JetstreamTeam
{
    public function members()
    {
        // N+1問題を防ぐためのEager Loading
        return $this->users()
            ->with(['profile', 'permissions'])
            ->withCount(['activities' => function ($query) {
                $query->where('created_at', '>=', now()->subDays(30));
            }]);
    }

    // キャッシュを活用したメンバー数の取得
    public function getMemberCountAttribute()
    {
        $cacheKey = "team_{$this->id}_member_count";

        return Cache::remember($cacheKey, now()->addHours(1), function () {
            return $this->users()->count();
        });
    }
}

2. キャッシュ戦略の実装

// app/Providers/AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // グローバルキャッシュ設定
        if (config('app.env') === 'production') {
            View::cache();
            Route::cache();
        }

        // モデルイベントでキャッシュをクリア
        Team::updated(function ($team) {
            Cache::tags(['team_' . $team->id])->flush();
        });
    }
}

// app/Http/Controllers/TeamDashboardController.php
class TeamDashboardController extends Controller
{
    public function show(Team $team)
    {
        $cacheKey = "team_dashboard_{$team->id}";

        $dashboardData = Cache::tags(['team_' . $team->id])->remember(
            $cacheKey,
            now()->addMinutes(15),
            function () use ($team) {
                return [
                    'members' => $team->members()->paginate(10),
                    'activities' => $team->recentActivities(),
                    'statistics' => $this->calculateTeamStatistics($team),
                ];
            }
        );

        return view('teams.dashboard', $dashboardData);
    }
}

よくあるエラーとその解決方法

1. 認証関連のトラブル対処

// app/Exceptions/Handler.php
class Handler extends ExceptionHandler
{
    public function register()
    {
        $this->renderable(function (AuthenticationException $e, $request) {
            if ($request->expectsJson()) {
                return response()->json([
                    'error' => 'Unauthenticated.',
                    'message' => '認証セッションが切れました。再度ログインしてください。'
                ], 401);
            }
        });

        // セッション切れの検出と処理
        $this->renderable(function (TokenMismatchException $e, $request) {
            return redirect()->route('login')
                ->withErrors(['session' => 'セッションが切れました。再度ログインしてください。']);
        });
    }
}

// セッショントラブルの診断ヘルパー
class SessionDiagnostics
{
    public static function checkSessionConfiguration()
    {
        $issues = [];

        if (!ini_get('session.save_handler')) {
            $issues[] = 'セッションハンドラが設定されていません。';
        }

        if (!is_writable(config('session.files'))) {
            $issues[] = 'セッションディレクトリに書き込み権限がありません。';
        }

        return $issues;
    }
}

2. データベース接続エラーの処理

// app/Providers/DatabaseServiceProvider.php
class DatabaseServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // データベース接続エラーの監視
        DB::listen(function ($query) {
            if ($query->time > 1000) {  // 1秒以上かかるクエリを検出
                Log::warning('スロークエリを検出:', [
                    'sql' => $query->sql,
                    'time' => $query->time,
                    'bindings' => $query->bindings,
                ]);
            }
        });

        // デッドロック検出
        DB::connection()->beforeExecuting(function ($query) {
            // クエリタイムアウトの設定
            DB::statement('SET SESSION wait_timeout = 28800');
        });
    }
}

アップデート時の互換性確認ポイント

1. アップデート前の確認事項

// app/Console/Commands/PreUpdateCheck.php
class PreUpdateCheck extends Command
{
    protected $signature = 'jetstream:pre-update-check';

    public function handle()
    {
        $this->info('Jetstreamアップデート前の確認を開始します...');

        // カスタマイズファイルの確認
        $customizations = $this->checkCustomizations();
        if ($customizations->isNotEmpty()) {
            $this->warn('以下のファイルがカスタマイズされています:');
            $customizations->each(fn($file) => $this->line("- {$file}"));
        }

        // 依存パッケージの互換性確認
        $this->checkDependencies();

        // データベースバックアップの確認
        if (!$this->confirmBackup()) {
            $this->error('データベースバックアップを先に実行してください。');
            return 1;
        }
    }

    private function checkCustomizations()
    {
        $publishedFiles = [
            resource_path('views/vendor/jetstream'),
            resource_path('views/layouts'),
            resource_path('js/Components'),
        ];

        return collect($publishedFiles)
            ->filter(fn($path) => File::exists($path));
    }
}

2. アップデート後の検証

// app/Console/Commands/PostUpdateVerification.php
class PostUpdateVerification extends Command
{
    public function handle()
    {
        $this->info('アップデート後の検証を開始します...');

        // 必要なテーブルの存在確認
        $this->verifyDatabaseTables();

        // 認証機能の動作確認
        $this->verifyAuthentication();

        // アセットのコンパイル確認
        $this->verifyAssets();
    }

    private function verifyDatabaseTables()
    {
        $requiredTables = [
            'users', 'teams', 'team_user', 'sessions',
            'personal_access_tokens',
        ];

        foreach ($requiredTables as $table) {
            if (!Schema::hasTable($table)) {
                $this->error("テーブル '{$table}' が見つかりません。");
                $this->info("php artisan migrate を実行してください。");
            }
        }
    }
}

実運用時の重要なポイント

  1. 監視とログ管理
  • エラーログの定期的な確認
  • パフォーマンスメトリクスの収集
  • ユーザーアクティビティの監視
  1. バックアップ戦略
  • 定期的なデータベースバックアップ
  • 設定ファイルのバージョン管理
  • リストア手順の文書化
  1. スケーラビリティ対策
  • 水平スケーリングの準備
  • キャッシュ戦略の最適化
  • セッション管理の分散化

これらの対策を実施することで、Jetstreamアプリケーションの安定運用が可能になります。特に、実運用環境では定期的なモニタリングとメンテナンスが重要です。