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
}