Method 1: Environment Variables
// api/config.php
define('FASTWAY_API_KEY', getenv('FASTWAY_API_KEY') ?: 'fallback_key');Method 2: Server-Side Only
- API key NEVER sent to client
- All API calls made from server-side PHP
- JavaScript cannot access the key
Method 3: File Protection (.htaccess)
# Deny direct access to config files
<Files "config.php">
Require all denied
</Files>
<FilesMatch "\.env$">
Require all denied
</FilesMatch>Method 4: Google App Engine (app.yaml)
env_variables:
FASTWAY_API_KEY: 'your_secure_key_here'Method 5: Google Secret Manager (Production)
use Google\Cloud\SecretManager\V1\SecretManagerServiceClient;
$client = new SecretManagerServiceClient();
$name = 'projects/PROJECT_ID/secrets/fastway-api-key/versions/latest';
$response = $client->accessSecretVersion($name);
$apiKey = $response->getPayload()->getData();- Not in version control (.gitignore)
- Not in client-side code
- Environment variable based
- File access restrictions
- Secret management for production
// api/config.php
function sanitizeInput($input, $type = 'general') {
if (empty($input)) {
return '';
}
// Basic sanitization
$input = trim($input);
$input = stripslashes($input);
$input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
// Type-specific sanitization
switch($type) {
case 'tracking':
// Only alphanumeric
return preg_replace('/[^a-zA-Z0-9]/', '', $input);
case 'postal':
// Only digits, max 4
$postal = preg_replace('/[^0-9]/', '', $input);
return substr($postal, 0, 4);
case 'numeric':
// Float validation
return filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT,
FILTER_FLAG_ALLOW_FRACTION);
case 'email':
return filter_var($input, FILTER_SANITIZE_EMAIL);
case 'suburb':
// Letters, spaces, hyphens only
return preg_replace('/[^a-zA-Z\s\-]/', '', $input);
default:
return $input;
}
}
function validateInput($input, $type, $rules = []) {
switch($type) {
case 'tracking':
// Must be 8-15 alphanumeric characters
return preg_match('/^[A-Z0-9]{8,15}$/i', $input);
case 'postal':
// Exactly 4 digits
return preg_match('/^[0-9]{4}$/', $input);
case 'weight':
// Between 0.1 and 30 kg
$weight = floatval($input);
return $weight >= 0.1 && $weight <= 30;
case 'suburb':
// 2-50 characters, letters and spaces
return strlen($input) >= 2 && strlen($input) <= 50;
default:
return !empty($input);
}
}// api/track.php
$trackingNumber = sanitizeInput($_POST['tracking_number'] ?? '', 'tracking');
if (!validateInput($trackingNumber, 'tracking')) {
sendResponse(false, [], 'Invalid tracking number format');
exit;
}
// api/quote.php
$suburb = sanitizeInput($_POST['suburb'] ?? '', 'suburb');
$postalCode = sanitizeInput($_POST['postal_code'] ?? '', 'postal');
$weight = sanitizeInput($_POST['weight'] ?? '', 'numeric');
if (!validateInput($suburb, 'suburb')) {
sendResponse(false, [], 'Invalid suburb name');
exit;
}
if (!validateInput($postalCode, 'postal')) {
sendResponse(false, [], 'Invalid postal code');
exit;
}
if (!validateInput($weight, 'weight')) {
sendResponse(false, [], 'Weight must be between 0.1 and 30 kg');
exit;
}Layer 1: Client-Side (UX)
// assets/js/track.js
if (!trackingNum || trackingNum.length < 3) {
showError('Please enter a valid tracking number');
return;
}Layer 2: Server-Side Sanitization
$input = sanitizeInput($input, $type);Layer 3: Server-Side Validation
if (!validateInput($input, $type)) {
sendResponse(false, [], 'Invalid input');
}Layer 4: Output Encoding
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');Risk: Key leaked in client code
Impact: Unauthorized API usage, financial cost
Mitigation: ✅ Server-side only, environment variables
Risk: Malicious JavaScript injection
Impact: Session hijacking, data theft
Mitigation:
// Always escape output
echo htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
// Content Security Policy
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net");Risk: Malicious SQL in user input
Impact: Data breach, data loss
Mitigation:
// Use prepared statements
$stmt = $pdo->prepare("SELECT * FROM tracking WHERE number = :number");
$stmt->execute(['number' => $trackingNumber]);Risk: Unauthorized actions
Impact: Unwanted API calls
Mitigation:
// Generate token
session_start();
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// Validate token
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
http_response_code(403);
exit('CSRF validation failed');
}Risk: DoS, scraping
Impact: High costs, service disruption
Mitigation: Rate limiting, CAPTCHA
Risk: Error messages reveal system details
Impact: Aids attackers
Mitigation:
// Production error handling
if (getenv('APP_ENV') === 'production') {
ini_set('display_errors', 0);
error_reporting(0);
}
// Generic error messages to users
try {
// ... code
} catch (Exception $e) {
// Log detailed error
logError($e->getMessage());
// Generic message to user
sendResponse(false, [], 'An error occurred. Please try again.');
}Risk: Intercepted communications
Impact: Data theft
Mitigation:
// Force HTTPS
if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') {
header('Location: https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
exit;
}
// HSTS Header
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');Risk: Stolen session cookies
Impact: Unauthorized access
Mitigation:
// Secure session configuration
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');
session_start();Method 1: Rate Limiting
class RateLimiter {
private $redis;
public function check($identifier, $limit = 100, $window = 3600) {
$key = "rate_limit:{$identifier}";
$current = $this->redis->incr($key);
if ($current === 1) {
$this->redis->expire($key, $window);
}
if ($current > $limit) {
http_response_code(429);
echo json_encode(['error' => 'Too many requests']);
exit;
}
return true;
}
}
// Usage in api/track.php
$rateLimiter = new RateLimiter();
$clientIp = $_SERVER['REMOTE_ADDR'];
$rateLimiter->check($clientIp, 100, 3600); // 100 requests/hourMethod 2: Token Bucket Algorithm
class TokenBucket {
private $capacity = 10; // Max requests
private $refillRate = 1; // Tokens per second
private $tokens;
private $lastRefill;
public function consume($count = 1) {
$this->refill();
if ($this->tokens >= $count) {
$this->tokens -= $count;
return true;
}
return false; // Rate limit exceeded
}
private function refill() {
$now = time();
$elapsed = $now - $this->lastRefill;
$tokensToAdd = $elapsed * $this->refillRate;
$this->tokens = min($this->capacity, $this->tokens + $tokensToAdd);
$this->lastRefill = $now;
}
}Method 3: API Key-Based Limits
class APIKeyLimiter {
public function checkLimit($apiKey) {
$usage = $this->getUsage($apiKey);
$plan = $this->getPlan($apiKey);
if ($usage >= $plan['max_requests']) {
http_response_code(429);
echo json_encode(['error' => 'API quota exceeded']);
exit;
}
}
}Method 1: Google reCAPTCHA
<!-- In form -->
<script src="https://www.google.com/recaptcha/api.js"></script>
<div class="g-recaptcha" data-sitekey="YOUR_SITE_KEY"></div>// Verify in PHP
function verifyCaptcha($response) {
$secret = getenv('RECAPTCHA_SECRET');
$verify = file_get_contents(
"https://www.google.com/recaptcha/api/siteverify?secret={$secret}&response={$response}"
);
$data = json_decode($verify);
return $data->success;
}
if (!empty($_POST['g-recaptcha-response'])) {
if (!verifyCaptcha($_POST['g-recaptcha-response'])) {
sendResponse(false, [], 'CAPTCHA verification failed');
exit;
}
}Method 2: Honeypot Field
<!-- Hidden field that bots will fill -->
<input type="text" name="website" style="display:none" tabindex="-1" autocomplete="off">// Reject if honeypot is filled
if (!empty($_POST['website'])) {
// Bot detected - reject silently
exit;
}Method 3: Timing Check
session_start();
$_SESSION['form_loaded_at'] = time();
// On submit
$timeTaken = time() - $_SESSION['form_loaded_at'];
if ($timeTaken < 2) {
// Too fast - likely a bot
exit;
}Method 4: Request Fingerprinting
function generateFingerprint() {
return hash('sha256', implode('|', [
$_SERVER['REMOTE_ADDR'],
$_SERVER['HTTP_USER_AGENT'],
$_SERVER['HTTP_ACCEPT_LANGUAGE']
]));
}
// Track fingerprints
$fingerprint = generateFingerprint();
$requestCount = $redis->incr("fp:{$fingerprint}");
if ($requestCount > 10) {
// Suspicious activity
exit;
}1. SQL Injection Prevention
// ✅ CORRECT - Prepared Statements
$stmt = $pdo->prepare("SELECT * FROM tracking WHERE number = :number");
$stmt->execute(['number' => $trackingNumber]);
// ❌ WRONG - String concatenation
$query = "SELECT * FROM tracking WHERE number = '{$trackingNumber}'";2. XSS Prevention
// ✅ CORRECT - Always escape output
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// ❌ WRONG - Direct output
echo $userInput;3. Command Injection Prevention
// ✅ CORRECT - Validate and whitelist
$allowedCommands = ['convert', 'resize'];
if (in_array($command, $allowedCommands)) {
exec(escapeshellcmd($command));
}
// ❌ WRONG - Direct execution
exec($_POST['command']);4. Path Traversal Prevention
// ✅ CORRECT - Validate path
$filename = basename($_GET['file']);
$filepath = __DIR__ . '/uploads/' . $filename;
if (realpath($filepath) !== $filepath) {
die('Invalid file path');
}
// ❌ WRONG - Direct path
$file = __DIR__ . '/' . $_GET['file'];5. LDAP Injection Prevention
// ✅ CORRECT - Escape special chars
function escapeLDAP($str) {
$metaChars = ['\\', '*', '(', ')', "\x00"];
$quotedMetaChars = [];
foreach ($metaChars as $char) {
$quotedMetaChars[] = '\\' . sprintf('%02x', ord($char));
}
return str_replace($metaChars, $quotedMetaChars, $str);
}6. XML Injection Prevention
// ✅ CORRECT - Disable external entities
libxml_disable_entity_loader(true);
$xml = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOENT);1. Data Encryption
class EncryptionService {
private $cipher = 'AES-256-CBC';
private $key;
public function __construct() {
$this->key = getenv('ENCRYPTION_KEY');
}
public function encrypt($data) {
$iv = openssl_random_pseudo_bytes(16);
$encrypted = openssl_encrypt($data, $this->cipher, $this->key, 0, $iv);
return base64_encode($iv . $encrypted);
}
public function decrypt($data) {
$data = base64_decode($data);
$iv = substr($data, 0, 16);
$encrypted = substr($data, 16);
return openssl_decrypt($encrypted, $this->cipher, $this->key, 0, $iv);
}
}2. Data Minimization
// Only collect necessary data
$tracking = [
'tracking_number' => $trackingNumber,
'status' => $status,
'searched_at' => time()
// Don't store: names, addresses, phone numbers unless required
];3. Data Retention Policy
class DataRetentionService {
public function cleanOldData() {
// Delete data older than retention period (e.g., 12 months)
$pdo->exec("
DELETE FROM tracking_history
WHERE created_at < DATE_SUB(NOW(), INTERVAL 12 MONTH)
");
// Log deletion
$this->logDeletion('tracking_history', $pdo->rowCount());
}
}
// Run via cron job
// 0 0 * * 0 php /path/to/cleanup.php4. Consent Management
class ConsentManager {
public function recordConsent($userId, $purpose, $consented) {
$stmt = $pdo->prepare("
INSERT INTO user_consents (user_id, purpose, consented, consented_at, ip_address)
VALUES (?, ?, ?, NOW(), ?)
");
$stmt->execute([
$userId,
$purpose,
$consented ? 1 : 0,
$_SERVER['REMOTE_ADDR']
]);
}
public function hasConsent($userId, $purpose) {
$stmt = $pdo->prepare("
SELECT consented FROM user_consents
WHERE user_id = ? AND purpose = ?
ORDER BY consented_at DESC LIMIT 1
");
$stmt->execute([$userId, $purpose]);
$result = $stmt->fetch();
return $result && $result['consented'] == 1;
}
}5. Right to be Forgotten
class DataDeletionService {
public function deleteUserData($userId) {
$pdo->beginTransaction();
try {
// Delete from all tables
$tables = ['tracking_history', 'quotes', 'user_consents', 'users'];
foreach ($tables as $table) {
$pdo->prepare("DELETE FROM {$table} WHERE user_id = ?")
->execute([$userId]);
}
// Log deletion for compliance
$pdo->prepare("
INSERT INTO deletion_log (user_id, deleted_at, reason, deleted_by)
VALUES (?, NOW(), 'User request - GDPR', ?)
")->execute([$userId, getCurrentAdminId()]);
$pdo->commit();
return true;
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
}
}6. Data Access Request
class DataAccessService {
public function exportUserData($userId) {
$data = [];
// Get all user data
$data['tracking_history'] = $this->getTrackingHistory($userId);
$data['quote_history'] = $this->getQuoteHistory($userId);
$data['consents'] = $this->getConsents($userId);
// Generate JSON export
$json = json_encode($data, JSON_PRETTY_PRINT);
// Log access request
$this->logAccessRequest($userId);
return $json;
}
}7. Audit Logging
class AuditLogger {
public function logAccess($userId, $action, $data) {
$stmt = $pdo->prepare("
INSERT INTO audit_log
(user_id, action, data, ip_address, user_agent, accessed_at)
VALUES (?, ?, ?, ?, ?, NOW())
");
$stmt->execute([
$userId,
$action,
json_encode($data),
$_SERVER['REMOTE_ADDR'],
$_SERVER['HTTP_USER_AGENT']
]);
}
}
// Log every data access
$auditLogger->logAccess($userId, 'tracking_search', ['number' => $trackingNumber]);8. Privacy Policy & Cookie Consent
// Display privacy banner
if (!isset($_COOKIE['privacy_accepted'])) {
// Show banner
}
// Privacy policy page
class PrivacyPolicy {
public function getPolicy() {
return [
'data_collected' => ['tracking numbers', 'search history'],
'purpose' => 'Provide tracking services',
'retention' => '12 months',
'rights' => ['access', 'rectification', 'erasure', 'portability'],
'contact' => 'privacy@company.com'
];
}
}9. Data Breach Notification
class BreachNotificationService {
public function notifyBreach($affectedUsers, $breachDetails) {
// Notify within 72 hours (GDPR requirement)
foreach ($affectedUsers as $user) {
$this->sendBreachNotification($user, $breachDetails);
}
// Notify data protection authority
$this->notifyDPA($breachDetails);
// Log notification
$this->logBreachNotification($breachDetails);
}
}- Lawful basis for processing
- Consent mechanism
- Data minimization
- Purpose limitation
- Storage limitation (retention policy)
- Security measures (encryption)
- Data subject rights (access, erasure)
- Breach notification procedures
- Privacy by design
- Data protection officer (if required)
- Privacy policy published
- Audit logging
- ✅ Sanitize ALL user input
- ✅ Validate on both client and server
- ✅ Escape ALL output
- ✅ Use prepared statements for SQL
- ✅ Use strong session management
- ✅ Implement CSRF protection
- ✅ Use secure cookies
- ✅ Rate limit login attempts
- ✅ Keep keys server-side only
- ✅ Use environment variables
- ✅ Implement rate limiting
- ✅ Add request signing (optional)
- ✅ Encrypt sensitive data
- ✅ Use HTTPS only
- ✅ Implement data retention
- ✅ Enable audit logging
- ✅ Never expose system details
- ✅ Log errors securely
- ✅ Use generic error messages
- ✅ Monitor for anomalies
header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
header('X-XSS-Protection: 1; mode=block');
header('Strict-Transport-Security: max-age=31536000');
header('Content-Security-Policy: default-src \'self\'');
header('Referrer-Policy: no-referrer');Security is a continuous process, not a one-time setup!