Protecting Against API Abuse
Protecting Against API Abuse
Mobile APIs are vulnerable to various forms of abuse that must be mitigated.
// Android - API abuse prevention
class APIProtectionManager {
// Implement request signing to prevent tampering
class RequestSigner(private val apiSecret: String) {
fun signRequest(request: Request): Request {
val timestamp = System.currentTimeMillis()
val nonce = UUID.randomUUID().toString()
// Create canonical request string
val canonicalRequest = buildCanonicalRequest(request, timestamp, nonce)
// Generate signature
val signature = generateSignature(canonicalRequest)
// Add headers to request
return request.newBuilder()
.header("X-Timestamp", timestamp.toString())
.header("X-Nonce", nonce)
.header("X-Signature", signature)
.build()
}
private fun buildCanonicalRequest(
request: Request,
timestamp: Long,
nonce: String
): String {
val method = request.method
val path = request.url.encodedPath
val query = request.url.encodedQuery ?: ""
// Include body hash if present
val bodyHash = request.body?.let { body ->
val buffer = Buffer()
body.writeTo(buffer)
val bytes = buffer.readByteArray()
MessageDigest.getInstance("SHA-256")
.digest(bytes)
.joinToString("") { "%02x".format(it) }
} ?: ""
return "$method\n$path\n$query\n$timestamp\n$nonce\n$bodyHash"
}
private fun generateSignature(canonicalRequest: String): String {
val hmac = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(apiSecret.toByteArray(), "HmacSHA256")
hmac.init(secretKey)
val hash = hmac.doFinal(canonicalRequest.toByteArray())
return Base64.encodeToString(hash, Base64.NO_WRAP)
}
}
// Device attestation for API access
class DeviceAttestationManager(private val context: Context) {
fun generateAttestationToken(): String? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
// Use SafetyNet Attestation API
val nonce = generateNonce()
val client = SafetyNet.getClient(context)
val task = client.attest(nonce, BuildConfig.SAFETY_NET_API_KEY)
val result = Tasks.await(task)
// Send JWS result to server for verification
result.jwsResult
} catch (e: Exception) {
null
}
} else {
// Fallback for older devices
generateBasicDeviceFingerprint()
}
}
private fun generateNonce(): ByteArray {
val nonce = ByteArray(32)
SecureRandom().nextBytes(nonce)
return nonce
}
private fun generateBasicDeviceFingerprint(): String {
val fingerprint = StringBuilder()
fingerprint.append(Build.MANUFACTURER).append("|")
fingerprint.append(Build.MODEL).append("|")
fingerprint.append(Build.VERSION.RELEASE).append("|")
fingerprint.append(getAppSignature())
return Base64.encodeToString(
fingerprint.toString().toByteArray(),
Base64.NO_WRAP
)
}
}
}