getTokenFromRequest($request); if (!$token) { return response()->json(['error' => 'Token not provided'], 401); } try { $decoded = $this->decodeToken($token); } catch (\Exception $e) { return response()->json(['error' => 'Invalid token'], 401); } // Check token blacklist if ($this->isTokenBlacklisted($decoded)) { return response()->json(['error' => 'Token has been revoked'], 401); } // Validate scopes if specified if (!empty($scopes) && !$this->hasScopes($decoded, $scopes)) { return response()->json(['error' => 'Insufficient scopes'], 403); } // Check expiration if ($decoded['exp'] < time()) { // Add to blacklist on expiry $this->blacklistToken($decoded); return response()->json(['error' => 'Token expired'], 401); } // Set tenant ID on request $request->merge(['tenant_id' => $decoded['sub']]); $request->attributes->set('decoded_token', $decoded); return $next($request); } /** * Get token from request (Bearer or header) */ private function getTokenFromRequest(Request $request): ?string { $header = $request->header('Authorization'); if (str_starts_with($header, 'Bearer ')) { return substr($header, 7); } if ($request->header('X-API-Token')) { return $request->header('X-API-Token'); } return null; } /** * Decode JWT token */ private function decodeToken(string $token): array { $publicKey = file_get_contents(storage_path('app/public.key')); if (!$publicKey) { throw new \Exception('JWT public key not found'); } $decoded = JWT::decode($token, new Key($publicKey, 'RS256')); return (array) $decoded; } /** * Check if token is blacklisted */ private function isTokenBlacklisted(array $decoded): bool { $jti = isset($decoded['jti']) ? $decoded['jti'] : md5($decoded['sub'] . $decoded['iat']); $cacheKey = "blacklisted_token:{$jti}"; // Check cache first (faster) if (Cache::has($cacheKey)) { return true; } // Check DB blacklist $dbJti = $decoded['jti'] ?? null; $blacklisted = TenantToken::where('jti', $dbJti) ->orWhere('token_hash', md5($decoded['sub'] . $decoded['iat'])) ->where('expires_at', '>', now()) ->exists(); if ($blacklisted) { Cache::put($cacheKey, true, now()->addMinutes(5)); return true; } return false; } /** * Add token to blacklist */ private function blacklistToken(array $decoded): void { $jti = $decoded['jti'] ?? md5($decoded['sub'] . $decoded['iat']); $cacheKey = "blacklisted_token:{$jti}"; // Cache for immediate effect Cache::put($cacheKey, true, $decoded['exp'] - time()); // Store in DB for persistence TenantToken::updateOrCreate( [ 'jti' => $jti, 'tenant_id' => $decoded['sub'], ], [ 'token_hash' => md5(json_encode($decoded)), 'ip_address' => request()->ip(), 'user_agent' => request()->userAgent(), 'expires_at' => now()->addHours(24), // Keep for 24h after expiry ] ); } /** * Check if token has required scopes */ private function hasScopes(array $decoded, array $requiredScopes): bool { $tokenScopes = $decoded['scopes'] ?? []; if (!is_array($tokenScopes)) { $tokenScopes = explode(' ', $tokenScopes); } foreach ($requiredScopes as $scope) { if (!in_array($scope, $tokenScopes)) { return false; } } return true; } }