REST Endpoints and API Security
Secure ColdFusion REST APIs and web services with proper authentication, input validation, rate limiting, and structured logging for production deployments.
ColdFusion REST Services
ColdFusion provides built-in REST support through CFCs with remote access enabled:
// UserAPI.cfc component restpath="users" rest="true" { remote any function getUser(required numeric id) httpmethod="GET" { // Input validation if (arguments.id < 1) { restSetResponse({ status: 400, content: {"error": "Invalid user ID"} }); return; } // Business logic var user = getUserFromDB(arguments.id); if (isNull(user)) { restSetResponse({status: 404}); return {"error": "User not found"}; } return user; } remote any function createUser(required struct userData) httpmethod="POST" consumes="application/json" produces="application/json" { // Validate input if (!structKeyExists(arguments.userData, "email")) { restSetResponse({status: 400}); return {"error": "Email required"}; } // Create user var newUser = saveUser(arguments.userData); restSetResponse({status: 201}); return newUser; } }
Authentication and Authorization
Token-Based Authentication
// Validate JWT token in Application.cfc function onRequestStart(targetPage) { if (findNoCase("/api/", targetPage)) { var authHeader = getHTTPRequestData().headers["Authorization"] ?: ""; if (!len(authHeader) || !authHeader.startsWith("Bearer ")) { restSetResponse({status: 401}); writeOutput(serializeJSON({"error": "Unauthorized"})); return false; } var token = listLast(authHeader, " "); try { var payload = verifyJWT(token); request.user = payload; } catch (any e) { restSetResponse({status: 401}); writeOutput(serializeJSON({"error": "Invalid token"})); return false; } } return true; }
API Keys
// Simple API key validation function validateAPIKey(required string apiKey) { // Check against database or cache var validKey = cacheGet("api_key_#arguments.apiKey#"); if (isNull(validKey)) { var qKey = queryExecute( "SELECT id, rate_limit FROM api_keys WHERE key_hash = :hash AND active = 1", {hash: hash(arguments.apiKey, "SHA-256")}, {datasource: "mydb"} ); if (qKey.recordCount == 0) { return false; } cachePut("api_key_#arguments.apiKey#", qKey, createTimeSpan(0, 1, 0, 0)); validKey = qKey; } request.apiKeyData = validKey; return true; }
Input Validation
- Validate all inputs: Type, format, length, range
- Whitelist approach: Accept only known-good values
- SQL injection: Always use cfqueryparam
- XSS prevention: Encode output appropriately
- Schema validation: Validate JSON/XML against schema
// Input validation example function validateUserInput(required struct data) { var errors = []; // Email validation if (!structKeyExists(data, "email") || !reFindNoCase("^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$", data.email)) { arrayAppend(errors, "Invalid email format"); } // Age validation if (structKeyExists(data, "age") && (!isNumeric(data.age) || data.age < 0 || data.age > 150)) { arrayAppend(errors, "Invalid age"); } return errors; }
Rate Limiting
You should implement rate limiting to prevent API abuse and protect your server resources:
// Token bucket rate limiting function checkRateLimit(required string identifier, numeric limit=100, numeric window=60) { var cacheKey = "rate_limit_#arguments.identifier#"; var bucket = cacheGet(cacheKey); if (isNull(bucket)) { bucket = { tokens: arguments.limit, lastRefill: now() }; } else { // Refill tokens based on time elapsed var elapsed = dateDiff("s", bucket.lastRefill, now()); var tokensToAdd = int(elapsed * (arguments.limit / arguments.window)); bucket.tokens = min(bucket.tokens + tokensToAdd, arguments.limit); bucket.lastRefill = now(); } if (bucket.tokens < 1) { // Rate limit exceeded restSetResponse({ status: 429, headers: {"Retry-After": arguments.window} }); return false; } bucket.tokens--; cachePut(cacheKey, bucket, createTimeSpan(0, 0, arguments.window * 2, 0)); return true; }
Structured Logging with Request IDs
// Application.cfc function onRequestStart(targetPage) { // Generate request ID for tracing request.requestID = createUUID(); // Extract correlation ID from headers if present var headers = getHTTPRequestData().headers; request.correlationID = headers["X-Correlation-ID"] ?: request.requestID; // Log request writeLog( file="api_requests", type="information", text=serializeJSON({ requestID: request.requestID, correlationID: request.correlationID, method: cgi.request_method, path: cgi.path_info, userAgent: cgi.http_user_agent, ip: cgi.remote_addr }) ); } function onRequestEnd() { // Log response writeLog( file="api_requests", type="information", text=serializeJSON({ requestID: request.requestID, duration: getTickCount() - request.startTime, status: getPageContext().getResponse().getStatus() }) ); }
CORS Configuration
// Configure CORS headers function setCORSHeaders(allowedOrigin="*") { var response = getPageContext().getResponse(); response.setHeader("Access-Control-Allow-Origin", arguments.allowedOrigin); response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); response.setHeader("Access-Control-Max-Age", "3600"); // Handle preflight OPTIONS requests if (cgi.request_method == "OPTIONS") { restSetResponse({status: 204}); abort; } }
Legacy SOAP Services
Recommendation: Disable legacy SOAP services unless specifically required. Remove the SOAP cfpm package if not in use.
# Remove SOAP package if not needed cfpm remove soap
API Documentation and Versioning
- Include version numbers (v1, v2) in your API URL paths to support multiple versions
- Document all endpoints using OpenAPI/Swagger specifications
- Maintain backward compatibility whenever possible to avoid breaking existing clients
- Provide clear migration guides when you must introduce breaking changes
API Security Checklist
- Authentication implemented (tokens or API keys)
- All inputs validated and sanitized
- Rate limiting configured
- Request IDs logged for tracing
- CORS properly configured
- HTTPS only (no HTTP)
- Error messages don't leak sensitive info
- Legacy SOAP disabled if not needed
- API versioning strategy in place
Gotchas
- Missing authentication allows public access to API endpoints
- Insufficient input validation enables injection attacks
- No rate limiting allows abuse and DDoS attacks
- Verbose error messages leak implementation details
- CORS wildcard (*) allows any origin - use specific domains
- Missing request IDs makes debugging distributed systems difficult
Need Help?
Convective can help design and secure REST APIs in ColdFusion. Find out more.