API Security: Best Practices for Developers

Authentication, rate limiting, input validation, CORS — 10 security practices for every API. With code examples.
API Security Best Practices
An unprotected API is an open door to your company's data. Every day, unprotected APIs face automated intrusion attempts. This guide covers the essential protection you must apply to any API.
Most Common API Security Issues
According to OWASP API Security Top 10 (2023):
- Broken Object Level Authorization — Most common: a user accessing another user's data
- Broken Authentication — Weak or broken authentication
- Excessive Data Exposure — Returning more data than the client needs
- Insufficient Rate Limiting — Allowing unlimited requests
- Mass Assignment — Accepting any field in the request without filtering
Authentication
Secure JWT
// Laravel - Secure JWT example
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// Create Token
$payload = [
'sub' => $user->id,
'iat' => time,
'exp' => time + 3600, // Expires in one hour
'jti' => uniqid('', true), // Prevent replay attacks
];
$token = JWT::encode($payload, config('app.jwt_secret'), 'HS256');
// Verify Token
try {
$decoded = JWT::decode($token, new Key(config('app.jwt_secret'), 'HS256'));
$userId = $decoded->sub;
} catch (\Exception $e) {
return response->json(['error' => 'Unauthorized'], 401);
}
API Keys for External Services
// Generate a secure API Key
$apiKey = bin2hex(random_bytes(32)); // 64 random characters
// Store as Hash (never store the original key)
$hashedKey = hash('sha256', $apiKey);
// In verification middleware
public function handle(Request $request, Closure $next)
{
$apiKey = $request->header('X-API-Key');
if (!$apiKey) {
return response->json(['error' => 'API key required'], 401);
}
$hashedKey = hash('sha256', $apiKey);
$key = ApiKey::where('key_hash', $hashedKey)
->where('is_active', true)
->first;
if (!$key) {
return response->json(['error' => 'Invalid API key'], 401);
}
return $next($request);
}
Authorization: Permission Verification
Broken Object Level Authorization
// Common mistake — allows any user to see any invoice
public function show($invoiceId)
{
$invoice = Invoice::find($invoiceId); // Wrong!
return response->json($invoice);
}
// Correct — verify resource ownership
public function show($invoiceId)
{
$invoice = Invoice::where('id', $invoiceId)
->where('user_id', auth->id) // Correct
->firstOrFail;
return response->json($invoice);
}
Rate Limiting
// Laravel — Custom Rate Limiting
RateLimiter::for('api', function (Request $request) {
return [
Limit::perMinute(60)->by($request->user?->id ?: $request->ip),
Limit::perDay(10000)->by($request->user?->id ?: $request->ip),
];
});
Strict Rate Limiting for Sensitive Endpoints
// Strong protection for Login and Password Reset
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)
->by($request->input('email') . '|' . $request->ip)
->response(function {
return response->json([
'error' => 'Too many attempts. Try again in 1 minute.'
], 429);
});
});
Input Validation
// Strict validation of incoming data
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email:rfc,dns',
'amount' => 'required|numeric|min:0|max:100000',
'type' => 'required|in:invoice,receipt,quote',
]);
// Use $validated only — never $request->all
Invoice::create($validated);
}
Security Response Headers
// Middleware to add Security Headers
public function handle(Request $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');
$response->headers->set('Content-Security-Policy', "default-src 'self'");
// Hide server information
$response->headers->remove('X-Powered-By');
$response->headers->remove('Server');
return $response;
}
Logging Security Events
// Log all failed authentication attempts
Event::listen(Failed::class, function ($event) {
Log::warning('Failed login attempt', [
'email' => $event->credentials['email'],
'ip' => request->ip,
'user_agent' => request->userAgent,
'time' => now->toIso8601String,
]);
});
HTTPS Is Mandatory
Any API running on HTTP without TLS sends all data unencrypted. كلاود (cloud.net.sa) provides Let's Encrypt for free with automatic renewal — no excuse for an API without HTTPS.
# Redirect HTTP to HTTPS
server {
listen 80;
server_name api.yourcompany.com;
return 301 https://$host$request_uri;
}
Conclusion
API security is not optional — it is a legal and ethical responsibility. Start with HTTPS + JWT + Rate Limiting + Input Validation, then add additional layers based on the sensitivity of your data.
Get Started
Need a secure environment? Choose a protected server with DDoS protection and advanced firewall. Review our SLA and Privacy Policy.


