API Key Security
Storage
Never commit API keys to version control or expose them in client-side code.
// ❌ Bad: Hardcoded in source code
const API_KEY = "sk_live_abc123";
// ✅ Good: Use environment variables
const API_KEY = process.env.FLEXY_API_KEY;
Environment Variables
# .env file
FLEXY_API_KEY=sk_live_your_key_here
FLEXY_SANDBOX_KEY=sk_test_your_sandbox_key
API_BASE_URL=https://api.oneclickdz.com
// Load environment variables
require("dotenv").config();
const apiKey = process.env.FLEXY_API_KEY;
Key Rotation
// Support multiple keys for zero-downtime rotation
const API_KEYS = {
primary: process.env.FLEXY_API_KEY_PRIMARY,
secondary: process.env.FLEXY_API_KEY_SECONDARY,
};
async function callApiWithRotation(endpoint, data) {
try {
return await callApi(endpoint, data, API_KEYS.primary);
} catch (error) {
if (error.status === 401) {
// Try secondary key
return await callApi(endpoint, data, API_KEYS.secondary);
}
throw error;
}
}
HTTPS Enforcement
// Always use HTTPS
const API_BASE_URL = "https://api.oneclickdz.com";
// Reject self-signed certificates in production
const axios = require("axios");
const https = require("https");
const client = axios.create({
baseURL: API_BASE_URL,
httpsAgent: new https.Agent({
rejectUnauthorized: process.env.NODE_ENV === "production",
}),
});
Request Validation
// Validate all inputs before sending to API
const Joi = require("joi");
const mobileTopupSchema = Joi.object({
plan_code: Joi.string().required(),
MSSIDN: Joi.string()
.pattern(/^0[567][0-9]{8}$/)
.required(),
amount: Joi.number()
.min(50)
.max(5000)
.when("plan_code", {
is: Joi.string().pattern(/^DYN/),
then: Joi.required(),
otherwise: Joi.forbidden(),
}),
reference: Joi.string().max(100).required(),
});
function validateTopupRequest(data) {
const { error, value } = mobileTopupSchema.validate(data);
if (error) {
throw new Error(`Validation error: ${error.message}`);
}
return value;
}
Sensitive Data Handling
Don’t Log Sensitive Data
// ❌ Bad: Logs API key
console.log("Request:", {
headers: { "X-Access-Token": apiKey },
body: data,
});
// ✅ Good: Redact sensitive info
function sanitizeLog(data) {
const sanitized = { ...data };
if (sanitized.headers?.X-Access-Token) {
sanitized.headers.X-Access-Token = "***REDACTED***";
}
return sanitized;
}
console.log("Request:", sanitizeLog(request));
Protect Phone Numbers
// Mask phone numbers in logs/UI
function maskPhone(phone) {
if (phone.length !== 10) return phone;
return phone.slice(0, 4) + "****" + phone.slice(-2);
}
console.log("Top-up sent to:", maskPhone("0551234567")); // 0551****67
Secure Card Codes
// Never log full card codes
function maskCardCode(code) {
if (code.length <= 4) return "****";
return "****" + code.slice(-4);
}
// Store securely
await db.orders.create({
data: {
userId: user.id,
cardCode: encrypt(cardCode), // Encrypt sensitive data
cardCodeMasked: maskCardCode(cardCode),
},
});
Rate Limiting
// Implement client-side rate limiting
const rateLimit = require("express-rate-limit");
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 60, // Max 60 requests per minute
message: "Too many requests, please try again later",
});
app.use("/api/topup", apiLimiter, topupHandler);
IP Whitelisting
Contact support to whitelist your server IPs for additional security.
// Make requests from whitelisted IPs only
const WHITELISTED_IPS = ["192.168.1.100", "10.0.0.50"];
function isWhitelisted(ip) {
return WHITELISTED_IPS.includes(ip);
}
app.use((req, res, next) => {
const clientIp = req.ip;
if (!isWhitelisted(clientIp)) {
return res.status(403).json({ error: "Forbidden" });
}
next();
});
Audit Logging
// Log all API interactions for audit
async function auditLog(event, data) {
await db.auditLogs.create({
data: {
timestamp: new Date(),
event,
userId: data.userId,
action: data.action,
ipAddress: data.ip,
userAgent: data.userAgent,
requestId: data.requestId,
success: data.success,
// Don't log sensitive data
metadata: sanitize(data.metadata),
},
});
}
// Usage
await auditLog("topup_sent", {
userId: user.id,
action: "SEND_MOBILE_TOPUP",
ip: req.ip,
userAgent: req.get("user-agent"),
requestId: response.requestId,
success: true,
});
Error Messages
// Don't expose internal details in error messages
function getSafeErrorMessage(error) {
// Internal errors
if (error.status >= 500) {
return {
message: "An error occurred. Please try again later.",
code: "INTERNAL_ERROR",
requestId: error.requestId, // Include for support
};
}
// Client errors - be specific but safe
return {
message: error.message,
code: error.code,
requestId: error.requestId,
};
}
Secure Configuration
// Separate configs for sandbox/production
const config = {
sandbox: {
apiKey: process.env.FLEXY_SANDBOX_KEY,
baseUrl: "https://api.oneclickdz.com",
testMode: true,
logLevel: "debug",
},
production: {
apiKey: process.env.FLEXY_API_KEY,
baseUrl: "https://api.oneclickdz.com",
testMode: false,
logLevel: "error",
},
};
const env = process.env.NODE_ENV || "sandbox";
const currentConfig = config[env];
Dependency Security
# Regularly audit dependencies
npm audit
# Update vulnerable packages
npm audit fix
# Use tools like Snyk
npm install -g snyk
snyk test
Encryption at Rest
// Encrypt sensitive data before storing
const crypto = require("crypto");
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; // 32-byte key
const IV_LENGTH = 16;
function encrypt(text) {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(
"aes-256-cbc",
Buffer.from(ENCRYPTION_KEY),
iv
);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString("hex") + ":" + encrypted.toString("hex");
}
function decrypt(text) {
const parts = text.split(":");
const iv = Buffer.from(parts.shift(), "hex");
const encrypted = Buffer.from(parts.join(":"), "hex");
const decipher = crypto.createDecipheriv(
"aes-256-cbc",
Buffer.from(ENCRYPTION_KEY),
iv
);
let decrypted = decipher.update(encrypted);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
// Usage
const cardCode = "1234-5678-9012";
const encrypted = encrypt(cardCode);
await db.orders.create({ data: { cardCode: encrypted } });
Security Checklist
API Keys
✓ Stored in environment variables ✓ Not committed to version control ✓
Rotated periodically
HTTPS
✓ All requests use HTTPS ✓ Certificate validation enabled
Input Validation
✓ All inputs validated before API calls ✓ Schema validation in place
Logging
✓ Sensitive data not logged ✓ Audit logs enabled ✓ Request IDs tracked
Rate Limiting
✓ Client-side rate limiting ✓ Retry logic with backoff
Data Protection
✓ Sensitive data encrypted at rest ✓ Phone numbers masked in logs ✓ Card
codes secured
Best Practices Summary
Never Expose Keys
Use environment variables
Always Use HTTPS
Secure all communications
Validate Everything
Check inputs before sending
Encrypt Sensitive Data
Protect data at rest