API Security Best Practices
API Security Best Practices
Securing API communication requires multiple layers of protection beyond transport security.
API Key Management:
// iOS - Secure API key management
class APIKeyManager {
private struct Constants {
static let apiKeyTag = "com.app.apikey"
static let obfuscatedKey = "YXBpX2tleV9oZXJl" // Base64 encoded
}
// Never hardcode API keys directly
private func getAPIKey() -> String {
// Try to get from Keychain first
if let keyData = KeychainHelper.load(key: Constants.apiKeyTag),
let apiKey = String(data: keyData, encoding: .utf8) {
return apiKey
}
// Deobfuscate and store in Keychain
guard let decodedData = Data(base64Encoded: Constants.obfuscatedKey),
let apiKey = String(data: decodedData, encoding: .utf8) else {
fatalError("Failed to decode API key")
}
// Store in Keychain for future use
KeychainHelper.save(key: Constants.apiKeyTag, data: apiKey.data(using: .utf8)!)
return apiKey
}
// Generate request signature for additional security
func generateRequestSignature(
method: String,
path: String,
timestamp: TimeInterval,
body: Data?
) -> String {
let apiKey = getAPIKey()
// Create signature base string
var signatureBase = "\(method.uppercased())\n\(path)\n\(Int(timestamp))"
if let body = body {
let bodyHash = SHA256.hash(data: body)
let hashString = bodyHash.compactMap { String(format: "%02x", $0) }.joined()
signatureBase += "\n\(hashString)"
}
// Generate HMAC signature
let keyData = apiKey.data(using: .utf8)!
let signature = HMAC<SHA256>.authenticationCode(
for: signatureBase.data(using: .utf8)!,
using: SymmetricKey(data: keyData)
)
return Data(signature).base64EncodedString()
}
}
Request/Response Validation:
// Android - API request/response validation
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class APISecurityManager(private val context: Context) {
private val gson = Gson()
// Validate API responses
fun <T> validateAndParseResponse(
responseBody: String,
responseClass: Class<T>,
expectedSignature: String?
): T? {
// Verify response signature if provided
if (expectedSignature != null) {
val calculatedSignature = calculateResponseSignature(responseBody)
if (calculatedSignature != expectedSignature) {
throw SecurityException("Response signature mismatch")
}
}
// Parse JSON with validation
return try {
val response = gson.fromJson(responseBody, responseClass)
// Perform additional validation based on response type
if (response is SecureApiResponse) {
validateSecureResponse(response)
}
response
} catch (e: JsonSyntaxException) {
null
}
}
private fun calculateResponseSignature(responseBody: String): String {
val secret = getApiSecret()
val hmac = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(secret.toByteArray(), "HmacSHA256")
hmac.init(secretKey)
val hash = hmac.doFinal(responseBody.toByteArray())
return Base64.encodeToString(hash, Base64.NO_WRAP)
}
private fun validateSecureResponse(response: SecureApiResponse) {
// Validate timestamp to prevent replay attacks
val currentTime = System.currentTimeMillis()
val responseTime = response.timestamp
if (Math.abs(currentTime - responseTime) > 5 * 60 * 1000) { // 5 minutes
throw SecurityException("Response timestamp out of valid range")
}
// Validate nonce
if (!NonceManager.validateNonce(response.nonce)) {
throw SecurityException("Invalid or reused nonce")
}
}
// Rate limiting implementation
class RateLimiter {
private val requestCounts = mutableMapOf<String, MutableList<Long>>()
private val limits = mapOf(
"/api/login" to RateLimit(5, 60000), // 5 requests per minute
"/api/data" to RateLimit(100, 60000), // 100 requests per minute
"default" to RateLimit(60, 60000) // 60 requests per minute default
)
fun checkRateLimit(endpoint: String): Boolean {
val limit = limits[endpoint] ?: limits["default"]!!
val now = System.currentTimeMillis()
val requests = requestCounts.getOrPut(endpoint) { mutableListOf() }
// Remove old requests
requests.removeAll { now - it > limit.windowMs }
if (requests.size >= limit.maxRequests) {
return false // Rate limit exceeded
}
requests.add(now)
return true
}
data class RateLimit(val maxRequests: Int, val windowMs: Long)
}
}
interface SecureApiResponse {
val timestamp: Long
val nonce: String
}