Skip to main content

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

1

API Keys

✓ Stored in environment variables ✓ Not committed to version control ✓ Rotated periodically
2

HTTPS

✓ All requests use HTTPS ✓ Certificate validation enabled
3

Input Validation

✓ All inputs validated before API calls ✓ Schema validation in place
4

Logging

✓ Sensitive data not logged ✓ Audit logs enabled ✓ Request IDs tracked
5

Rate Limiting

✓ Client-side rate limiting ✓ Retry logic with backoff
6

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