【保存版】Laravel Sanctumで作る最強のAPI認証システム -導入から実装まで完全解説

Laravel Sanctumとは?最新の認証システムを詳しく解説

Laravel Sanctumは、SPAやモバイルアプリケーション、APIトークンベースの認証を簡単に実装できるLaravelの公式パッケージです。シンプルで軽量な認証システムでありながら、モダンなWeb開発に必要な機能を完備しています。

モダンなAPI認証の課題を解決するSanctumの特徴

現代のWeb開発では、以下のような認証に関する課題が存在します:

  1. 複数プラットフォームへの対応
  • Webアプリケーション(SPA)
  • モバイルアプリケーション
  • サードパーティAPIクライアント
  1. セキュリティ要件の複雑化
  • クロスサイトリクエストフォージェリ(CSRF)対策
  • クロスオリジンリソース共有(CORS)の適切な設定
  • トークンの安全な管理

Sanctumは、これらの課題に対して以下の特徴で解決を提供します:

  • 2つの認証システムの統合
  • トークンベースのAPI認証
  • SPAのためのクッキーベースセッション認証
  • シンプルな実装
  // トークン発行の例
  $token = $user->createToken('token-name');

  // API認証の例
  Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
      return $request->user();
  });
  • 柔軟なトークン管理
  • 複数デバイスでの同時ログイン対応
  • トークンへの権限付与機能
  • トークンの有効期限設定

従来のパスポートとの決定的な違いと選択基準

Laravel Passportと比較した際の主な違いは以下の通りです:

機能SanctumPassport
アーキテクチャ軽量・シンプルOAuth2完全準拠
セットアップ最小限の設定比較的複雑
用途SPAとAPIの統合認証本格的なOAuth2サーバー
パフォーマンス高速やや重い
データベース最小限のテーブル複数テーブルが必要

選択基準のポイント:

  1. Sanctumを選ぶべき場合
  • シンプルなAPI認証が必要
  • SPAとAPIの統合認証を実装したい
  • パフォーマンスを重視する
  • 開発速度を優先する
  1. Passportを選ぶべき場合
  • OAuth2の完全な機能が必要
  • サードパーティアプリケーションへの認証提供
  • 既存のOAuth2システムとの統合

まとめると、Laravel Sanctumは、モダンなWeb開発における認証の課題を、シンプルさと高機能性のバランスを取りながら解決する優れたソリューションです。特にSPAやモバイルアプリケーションとの連携を前提とした開発では、その真価を発揮します。

Laravel Sanctumのセットアップ方法を徹底解説

Laravel Sanctumの導入は、適切な手順で行うことで、安全で確実なセットアップが可能です。ここでは、環境要件の確認から初期設定まで、段階的に解説します。

必要な環境要件と依存関係の確認

Sanctumを導入する前に、以下の要件を満たしているか確認しましょう:

  1. 必須要件
  • PHP 7.3以上
  • Laravel 8.0以上
  • データベース(MySQL 5.7+, PostgreSQL 9.6+等)
  1. 推奨環境設定
   // php.ini の推奨設定
   session.cookie_httponly = 1
   session.cookie_secure = 1
   session.cookie_samesite = "Lax"
  1. フロントエンド要件(SPA利用時)
  • CORSの設定が可能なサーバー環境
  • セッションクッキーを処理できるクライアント

Composer での Sanctum インストールと初期設定

  1. パッケージのインストール
   composer require laravel/sanctum
  1. Sanctumの設定ファイルとマイグレーションファイルの公開
   php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
  1. 必要なミドルウェアの登録
   // app/Http/Kernel.php

   protected $middlewareGroups = [
       'web' => [
           // ...
       ],

       'api' => [
           \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
           'throttle:api',
           \Illuminate\Routing\Middleware\SubstituteBindings::class,
       ],
   ];

マイグレーションの実行と設定ファイルのカスタマイズ

  1. データベースマイグレーションの実行
   php artisan migrate

これにより、以下のテーブルが作成されます:

  • personal_access_tokens: APIトークンの管理用テーブル
  1. 設定ファイルのカスタマイズ
   // config/sanctum.php

   return [
       'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
           '%s%s',
           'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
           env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
       ))),

       'guard' => ['web'],

       'expiration' => null,  // トークンの有効期限(分)

       'middleware' => [
           'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
           'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
       ],
   ];
  1. セキュリティ設定の調整
   // config/cors.php

   return [
       'paths' => ['api/*', 'sanctum/csrf-cookie'],
       'allowed_methods' => ['*'],
       'allowed_origins' => ['*'],
       'allowed_origins_patterns' => [],
       'allowed_headers' => ['*'],
       'exposed_headers' => [],
       'max_age' => 0,
       'supports_credentials' => true,  // 重要: SPAで必須
   ];

セットアップ完了後の動作確認:

// routes/api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

// テスト用コマンド
php artisan test

これでLaravel Sanctumの基本的なセットアップは完了です。エラーが発生した場合は、以下の点を確認してください:

  • データベース接続設定の確認
  • マイグレーションの実行状態
  • 環境変数(.env)の設定
  • CORSの設定(SPA利用時)
  • ミドルウェアの登録状態

セットアップが完了したら、次のステップとしてユーザー認証の実装に進むことができます。

実践的なAPI認証の実装手順

Laravel Sanctumを使用したAPI認証の実装について、実践的な手順とベストプラクティスを解説します。

権限ベース認証の基本実装方法

  1. Userモデルの準備
   // app/Models/User.php
   use Laravel\Sanctum\HasApiTokens;

   class User extends Authenticatable
   {
       use HasApiTokens, HasFactory, Notifiable;

       // トークンに付与できる権限の定義
       public static $tokenAbilities = [
           'read',
           'write',
           'delete'
       ];
   }
  1. トークン発行処理の実装
   // app/Http/Controllers/Auth/TokenController.php
   class TokenController extends Controller
   {
       public function createToken(Request $request)
       {
           $request->validate([
               'token_name' => 'required|string',
               'abilities' => 'array|in:' . implode(',', User::$tokenAbilities)
           ]);

           $token = $request->user()->createToken(
               $request->token_name,
               $request->abilities ?? ['*']
           );

           return response()->json([
               'token' => $token->plainTextToken,
               'expires_at' => now()->addDays(config('sanctum.expiration_days'))
           ]);
       }

       public function revokeToken(Request $request)
       {
           // 現在のトークンを無効化
           $request->user()->currentAccessToken()->delete();

           return response()->json(['message' => 'Token revoked successfully']);
       }
   }

ユーザー登録・ログインAPIのエンドポイント作成

  1. ユーザー登録API
   // app/Http/Controllers/Auth/RegisterController.php
   class RegisterController extends Controller
   {
       public function register(Request $request)
       {
           $validated = $request->validate([
               'name' => 'required|string|max:255',
               'email' => 'required|string|email|max:255|unique:users',
               'password' => 'required|string|min:8|confirmed'
           ]);

           $user = User::create([
               'name' => $validated['name'],
               'email' => $validated['email'],
               'password' => Hash::make($validated['password'])
           ]);

           $token = $user->createToken('auth_token')->plainTextToken;

           return response()->json([
               'user' => $user,
               'access_token' => $token,
               'token_type' => 'Bearer'
           ], 201);
       }
   }
  1. ログインAPI
   // app/Http/Controllers/Auth/LoginController.php
   class LoginController extends Controller
   {
       public function login(Request $request)
       {
           $credentials = $request->validate([
               'email' => 'required|email',
               'password' => 'required'
           ]);

           if (!Auth::attempt($credentials)) {
               return response()->json([
                   'message' => 'Invalid login credentials'
               ], 401);
           }

           $user = $request->user();
           $token = $user->createToken('auth_token')->plainTextToken;

           return response()->json([
               'user' => $user,
               'access_token' => $token,
               'token_type' => 'Bearer'
           ]);
       }
   }

セキュアなセキュリティ管理とベストプラクティス

  1. トークン有効期限の管理
   // config/sanctum.php
   return [
       'expiration' => 60 * 24, // 24時間
       'token_prefix' => env('SANCTUM_TOKEN_PREFIX', 'sanctum_'),
   ];

   // app/Providers/AuthServiceProvider.php
   public function boot()
   {
       Sanctum::authenticateAccessTokensUsing(function ($token, $isValid) {
           if ($isValid && $token->created_at->lte(now()->subDays(7))) {
               return false;
           }
           return $isValid;
       });
   }
  1. セキュリティヘッダーの設定
   // app/Http/Middleware/SecurityHeaders.php
   class SecurityHeaders
   {
       public function handle($request, Closure $next)
       {
           $response = $next($request);

           $response->headers->set('X-Content-Type-Options', 'nosniff');
           $response->headers->set('X-Frame-Options', 'DENY');
           $response->headers->set('X-XSS-Protection', '1; mode=block');
           $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');

           return $response;
       }
   }
  1. レート制限の実装
   // routes/api.php
   Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
       Route::post('/actions/sensitive', 'ActionController@sensitive');
   });

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

  1. トークン管理
  • 適切な有効期限の設定
  • 未使用トークンの定期的なクリーンアップ
  • トークンの権限スコープの最小化
  1. エラーハンドリング
   // app/Exceptions/Handler.php
   public function render($request, Throwable $exception)
   {
       if ($exception instanceof AuthenticationException) {
           return response()->json([
               'error' => 'Unauthenticated',
               'message' => 'Please login to access this resource'
           ], 401);
       }

       return parent::render($request, $exception);
   }
  1. セキュリティチェックリスト
  • SSL/TLS通信の強制
  • セキュリティヘッダーの設定
  • レート制限の実装
  • 適切なバリデーションの実装
  • エラーメッセージの適切な制御

この実装により、セキュアで保守性の高いAPI認証システムを構築することができます。

SPAとモバイルアプリの認証対応

Laravel Sanctumは、SPAとモバイルアプリの両方に対して、シームレスな認証体験を提供します。ここでは、それぞれのプラットフォームに対する具体的な実装方法を解説します。

CORSの設定とCSRF保護の実装

  1. CORSの基本設定
   // config/cors.php
   return [
       'paths' => ['api/*', 'sanctum/csrf-cookie'],
       'allowed_methods' => ['*'],
       'allowed_origins' => [
           'http://localhost:3000',
           'http://localhost:8080',
           'https://your-frontend-domain.com'
       ],
       'allowed_headers' => ['*'],
       'exposed_headers' => [],
       'max_age' => 0,
       'supports_credentials' => true, // 重要: 必ずtrueに設定
   ];
  1. CSRF保護の設定
   // app/Http/Middleware/VerifyCsrfToken.php
   protected $except = [
       'sanctum/csrf-cookie',
       'api/mobile/*'  // モバイルAPIのエンドポイント
   ];
  1. セッション設定の調整
   // config/session.php
   return [
       'domain' => env('SESSION_DOMAIN', null),
       'secure' => env('SESSION_SECURE_COOKIE', true),
       'same_site' => 'lax',
   ];

VueとReactとの連携方法

  1. Vueでの実装例
   // Vue 3での実装
   import axios from 'axios'

   axios.defaults.withCredentials = true

   const login = async (email, password) => {
     // CSRF保護の初期化
     await axios.get('/sanctum/csrf-cookie')

     try {
       const response = await axios.post('/api/login', {
         email,
         password
       })

       // ログイン成功時の処理
       localStorage.setItem('user', JSON.stringify(response.data.user))
       return response.data
     } catch (error) {
       console.error('Login failed:', error)
       throw error
     }
   }

   // API呼び出しの例
   const fetchUserData = async () => {
     try {
       const response = await axios.get('/api/user')
       return response.data
     } catch (error) {
       if (error.response.status === 401) {
         // 未認証時の処理
         router.push('/login')
       }
     }
   }
  1. Reactでの実装例
   // React実装例
   import axios from 'axios'

   const api = axios.create({
     baseURL: process.env.REACT_APP_API_URL,
     withCredentials: true,
     headers: {
       'Content-Type': 'application/json',
       'Accept': 'application/json'
     }
   })

   const AuthProvider = ({ children }) => {
     const [user, setUser] = useState(null)
     const [loading, setLoading] = useState(true)

     const login = async (email, password) => {
       await axios.get('/sanctum/csrf-cookie')

       const response = await api.post('/login', {
         email,
         password
       })

       setUser(response.data.user)
       return response.data
     }

     const logout = async () => {
       await api.post('/logout')
       setUser(null)
     }

     // 認証状態の確認
     useEffect(() => {
       const checkAuth = async () => {
         try {
           const response = await api.get('/api/user')
           setUser(response.data)
         } catch (error) {
           setUser(null)
         } finally {
           setLoading(false)
         }
       }

       checkAuth()
     }, [])

     return (
       <AuthContext.Provider value={{ user, login, logout, loading }}>
         {children}
       </AuthContext.Provider>
     )
   }

モバイルアプリでの認証の実装例

  1. iOS Swift実装例
   class APIClient {
       static let baseURL = "https://api.your-domain.com"
       static var token: String?

       static func login(email: String, password: String) async throws -> User {
           let url = URL(string: "\(baseURL)/api/login")!
           var request = URLRequest(url: url)
           request.httpMethod = "POST"
           request.setValue("application/json", forHTTPHeaderField: "Content-Type")

           let body = ["email": email, "password": password]
           request.httpBody = try JSONSerialization.data(withJSONObject: body)

           let (data, response) = try await URLSession.shared.data(for: request)
           guard let httpResponse = response as? HTTPURLResponse,
                 httpResponse.statusCode == 200 else {
               throw APIError.invalidResponse
           }

           let loginResponse = try JSONDecoder().decode(LoginResponse.self, from: data)
           token = loginResponse.token
           return loginResponse.user
       }

       static func authenticatedRequest<T: Decodable>(_ endpoint: String) async throws -> T {
           guard let token = token else { throw APIError.unauthorized }

           let url = URL(string: "\(baseURL)/api/\(endpoint)")!
           var request = URLRequest(url: url)
           request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

           let (data, _) = try await URLSession.shared.data(for: request)
           return try JSONDecoder().decode(T.self, from: data)
       }
   }
  1. Android Kotlin実装例
   class ApiService {
       private val retrofit = Retrofit.Builder()
           .baseUrl("https://api.your-domain.com")
           .addConverterFactory(GsonConverterFactory.create())
           .client(createOkHttpClient())
           .build()

       private fun createOkHttpClient(): OkHttpClient {
           return OkHttpClient.Builder()
               .addInterceptor { chain ->
                   val original = chain.request()

                   val request = original.newBuilder()
                       .header("Accept", "application/json")
                       .method(original.method, original.body)
                       .apply {
                           UserPreferences.token?.let {
                               header("Authorization", "Bearer $it")
                           }
                       }
                       .build()

                   chain.proceed(request)
               }
               .build()
       }

       suspend fun login(email: String, password: String): LoginResponse {
           return retrofit.create(ApiInterface::class.java)
               .login(LoginRequest(email, password))
               .also {
                   UserPreferences.token = it.token
               }
       }
   }

これらの実装により、SPAやモバイルアプリからのセキュアな認証が可能になります。実装時の注意点として:

  1. セキュリティ考慮事項
  • トークンの安全な保存
  • HTTPS通信の強制
  • トークンの有効期限管理
  • リフレッシュトークンの実装
  1. エラーハンドリング
  • ネットワークエラー
  • 認証エラー
  • トークン期限切れ
  1. ユーザー体験の最適化
  • オフライン対応
  • 自動ログイン
  • セッション維持

実際の運用に向けた重要な設定と注意点

本番環境でLaravel Sanctumを運用する際の重要な設定と、実際の運用で発生しやすい問題への対処方法を解説します。

本番環境でのセキュリティ設定

  1. 環境変数の適切な設定
   # .env
   SESSION_SECURE_COOKIE=true
   SESSION_DOMAIN=.your-domain.com
   SANCTUM_STATEFUL_DOMAINS=your-frontend-domain.com
   SESSION_LIFETIME=120
   SANCTUM_EXPIRATION=60
  1. セキュリティヘッダーの設定
   // app/Http/Middleware/SecurityHeadersMiddleware.php
   class SecurityHeadersMiddleware
   {
       public function handle($request, Closure $next)
       {
           $response = $next($request);

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

           if (app()->environment('production')) {
               $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
           }

           return $response;
       }
   }
  1. 適切なログ設定
   // config/logging.php
   'channels' => [
       'stack' => [
           'driver' => 'stack',
           'channels' => ['daily', 'slack'],
           'ignore_exceptions' => false,
       ],
       'daily' => [
           'driver' => 'daily',
           'path' => storage_path('logs/laravel.log'),
           'level' => env('LOG_LEVEL', 'debug'),
           'days' => 14,
       ],
       'slack' => [
           'driver' => 'slack',
           'url' => env('LOG_SLACK_WEBHOOK_URL'),
           'username' => 'Laravel Log',
           'emoji' => ':boom:',
           'level' => env('LOG_LEVEL', 'critical'),
       ],
   ]

パフォーマンス最適化のためのキャッシュ戦略

  1. トークンキャッシュの実装
   // app/Providers/AuthServiceProvider.php
   use Illuminate\Support\Facades\Cache;

   public function boot()
   {
       Sanctum::authenticateAccessTokensUsing(function ($token, $isValid) {
           $cacheKey = 'sanctum_token_' . $token->id;

           return Cache::remember($cacheKey, now()->addMinutes(5), function () use ($token, $isValid) {
               return $isValid && !$token->expired();
           });
       });
   }
  1. 不要トークンの定期クリーンアップ
   // app/Console/Commands/CleanupTokens.php
   class CleanupTokens extends Command
   {
       protected $signature = 'sanctum:cleanup';

       public function handle()
       {
           // 期限切れトークンの削除
           $expiredTokens = PersonalAccessToken::where('created_at', '<', now()->subDays(30))
               ->orWhere('last_used_at', '<', now()->subDays(7))
               ->delete();

           // 使用されていないトークンの削除
           $unusedTokens = PersonalAccessToken::whereNull('last_used_at')
               ->where('created_at', '<', now()->subDays(1))
               ->delete();

           $this->info('Cleaned up expired and unused tokens.');
       }
   }
  1. レート制限の最適化
   // routes/api.php
   Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
       // 通常のAPI制限
       Route::get('/user', function () {
           return auth()->user();
       })->middleware('throttle:60,1');

       // 重要な操作の制限
       Route::post('/sensitive-operation', [Controller::class, 'handle'])
           ->middleware('throttle:3,1');
   });

トラブルシューティングと一般的な問題の解決方法

  1. 認証エラーの診断と対処
   // app/Exceptions/Handler.php
   public function render($request, Throwable $exception)
   {
       if ($exception instanceof AuthenticationException) {
           Log::debug('Authentication failed', [
               'ip' => $request->ip(),
               'user_agent' => $request->userAgent(),
               'headers' => $request->headers->all()
           ]);

           return response()->json([
               'error' => 'Unauthenticated',
               'debug_info' => app()->environment('local') ? [
                   'cookies_present' => $request->cookies->all() ? 'yes' : 'no',
                   'token_present' => $request->bearerToken() ? 'yes' : 'no',
               ] : null
           ], 401);
       }

       return parent::render($request, $exception);
   }
  1. よくある問題と解決方法 問題 原因 解決方法 CORS エラー ドメイン設定の不一致 config/cors.phpallowed_originsを確認 トークン認証失敗 セッション設定の問題 SESSION_DOMAINSANCTUM_STATEFUL_DOMAINSを確認 パフォーマンス低下 トークン数の増大 定期的なクリーンアップジョブを実行
  2. デバッグ用のヘルパー関数
   // app/Helpers/AuthDebugger.php
   class AuthDebugger
   {
       public static function diagnose(Request $request)
       {
           $issues = [];

           if (!$request->secure() && app()->environment('production')) {
               $issues[] = 'HTTPS is not enabled';
           }

           if (!$request->cookies->has(config('session.cookie'))) {
               $issues[] = 'Session cookie is missing';
           }

           if (!in_array($request->getHost(), config('sanctum.stateful'))) {
               $issues[] = 'Domain is not in stateful domains list';
           }

           return $issues;
       }
   }

運用時の重要なチェックポイント:

  1. 定期的な監視項目
  • アクティブトークン数
  • 認証失敗率
  • レート制限ヒット数
  • セッションストアのサイズ
  1. セキュリティ監査項目
  • 不正アクセスの検知
  • トークンの使用パターン
  • 異常なリクエストパターン
  1. パフォーマンス監視
  • レスポンスタイム
  • データベース負荷
  • キャッシュヒット率

これらの設定と対策により、本番環境での安定した運用が可能になります。

Laravel Sanctumを使った認証システムの発展的な使い方

Sanctumの基本機能を拡張し、より高度な認証システムを構築するための実装方法を解説します。

マルチデバイス対応の実装方法

  1. デバイス情報の管理
   // database/migrations/create_user_devices_table.php
   public function up()
   {
       Schema::create('user_devices', function (Blueprint $table) {
           $table->id();
           $table->foreignId('user_id')->constrained()->onDelete('cascade');
           $table->string('device_name');
           $table->string('device_type');
           $table->string('push_token')->nullable();
           $table->timestamp('last_active_at');
           $table->timestamps();
       });
   }

   // app/Models/UserDevice.php
   class UserDevice extends Model
   {
       protected $fillable = [
           'device_name',
           'device_type',
           'push_token',
           'last_active_at'
       ];

       protected $casts = [
           'last_active_at' => 'datetime'
       ];
   }
  1. デバイス認証の拡張
   // app/Http/Controllers/Auth/DeviceController.php
   class DeviceController extends Controller
   {
       public function register(Request $request)
       {
           $validated = $request->validate([
               'device_name' => 'required|string',
               'device_type' => 'required|in:ios,android,web',
               'push_token' => 'nullable|string'
           ]);

           $device = auth()->user()->devices()->create([
               'device_name' => $validated['device_name'],
               'device_type' => $validated['device_type'],
               'push_token' => $validated['push_token'],
               'last_active_at' => now()
           ]);

           $token = auth()->user()->createToken(
               $device->device_name,
               ['*'],
               now()->addYear()
           );

           return response()->json([
               'token' => $token->plainTextToken,
               'device' => $device
           ]);
       }

       public function listDevices()
       {
           return auth()->user()->devices()
               ->orderBy('last_active_at', 'desc')
               ->get();
       }

       public function revokeDevice($deviceId)
       {
           $device = auth()->user()->devices()->findOrFail($deviceId);
           $device->delete();

           // 関連するトークンの削除
           auth()->user()->tokens()
               ->where('name', $device->device_name)
               ->delete();

           return response()->json(['message' => 'Device removed successfully']);
       }
   }

トークンの有効期限と自動更新の実装

  1. トークン管理の拡張
   // app/Models/Concerns/HasAdvancedTokens.php
   trait HasAdvancedTokens
   {
       public function createTokenWithRefresh($name, array $abilities = ['*'])
       {
           $token = $this->createToken($name, $abilities, now()->addHours(2));

           $refreshToken = Str::random(64);
           Cache::put(
               "refresh_token_{$refreshToken}",
               $token->accessToken->id,
               now()->addDays(30)
           );

           return [
               'access_token' => $token->plainTextToken,
               'refresh_token' => $refreshToken,
               'expires_in' => 7200
           ];
       }

       public function refreshToken($refreshToken)
       {
           $tokenId = Cache::get("refresh_token_{$refreshToken}");

           if (!$tokenId) {
               throw new AuthenticationException('Invalid refresh token');
           }

           $oldToken = $this->tokens()->find($tokenId);

           if (!$oldToken) {
               Cache::forget("refresh_token_{$refreshToken}");
               throw new AuthenticationException('Token not found');
           }

           // 新しいトークンの発行
           $newToken = $this->createTokenWithRefresh(
               $oldToken->name,
               $oldToken->abilities
           );

           // 古いトークンの削除
           $oldToken->delete();
           Cache::forget("refresh_token_{$refreshToken}");

           return $newToken;
       }
   }
  1. 自動更新の実装
   // app/Http/Middleware/CheckTokenExpiration.php
   class CheckTokenExpiration
   {
       public function handle($request, Closure $next)
       {
           if (!$request->user() || !$request->user()->currentAccessToken()) {
               return $next($request);
           }

           $token = $request->user()->currentAccessToken();

           // トークンの有効期限が近い場合は自動更新
           if ($token->created_at->addHours(1)->isPast()) {
               $newToken = $request->user()->createToken(
                   $token->name,
                   $token->abilities,
                   now()->addHours(2)
               );

               $response = $next($request);

               return $response->header('New-Token', $newToken->plainTextToken);
           }

           return $next($request);
       }
   }

カスタムガードとポリシーの活用方法

  1. カスタムガードの実装
   // app/Auth/Guards/SanctumDeviceGuard.php
   class SanctumDeviceGuard extends TokenGuard
   {
       protected function validateToken($token)
       {
           if (!parent::validateToken($token)) {
               return false;
           }

           $device = UserDevice::where('device_name', $token->name)
               ->where('user_id', $token->tokenable_id)
               ->first();

           if (!$device) {
               return false;
           }

           $device->update(['last_active_at' => now()]);
           return true;
       }
   }

   // config/auth.php
   'guards' => [
       'sanctum-device' => [
           'driver' => 'sanctum-device',
           'provider' => 'users',
       ],
   ]
  1. 高度なポリシーの実装
   // app/Policies/DevicePolicy.php
   class DevicePolicy
   {
       public function manageSensitiveData(User $user, UserDevice $device)
       {
           // デバイスが信頼できる場合のみ許可
           return $device->last_active_at->diffInDays() < 30 &&
                  $device->user_id === $user->id;
       }

       public function revokeOtherDevices(User $user, UserDevice $device)
       {
           // プライマリデバイスのみに許可
           return $device->is_primary && $device->user_id === $user->id;
       }
   }
  1. アクセス制御の実装例
   // routes/api.php
   Route::middleware(['auth:sanctum-device'])->group(function () {
       Route::post('/sensitive-data', function (Request $request) {
           $device = UserDevice::where('device_name', $request->user()
               ->currentAccessToken()->name)
               ->firstOrFail();

           if ($request->user()->cannot('manageSensitiveData', $device)) {
               return response()->json([
                   'message' => 'This device is not authorized for this operation'
               ], 403);
           }

           // 処理の実行
       });
   });

この発展的な実装により、以下のような高度な機能が実現できます:

  1. セキュリティ強化
  • デバイスごとの詳細な権限管理
  • 不審なアクセスの検知と防止
  • トークンの適切なライフサイクル管理
  1. ユーザー体験の向上
  • シームレスなトークン更新
  • デバイス管理機能の提供
  • きめ細かいアクセス制御
  1. 運用管理の効率化
  • デバイスアクティビティの監視
  • 異常検知の自動化
  • トークンライフサイクルの自動管理