Security Best Practices
Best practices for securing your UGC API keys and tokens
Securing your UGC API keys and tokens is crucial to protect your business and customer data. Follow these best practices to ensure the security of your API credentials.
General Security Tips
Use HTTPS Everywhere
Always use HTTPS to protect data in transit:
// Good - HTTPS
const apiUrl = 'https://api.ugc.africa';
// ❌ Bad - HTTP (insecure)
const apiUrl = 'http://api.example.com';Environment Variables
Store all credentials in environment variables:
# .env file
UGC_API_KEY=UGSK_live_your_key_here
UGC_WEBHOOK_SECRET=whsec_your_secret_hereUse .gitignore
Always ignore sensitive files in version control:
# .gitignore
.env
.env.local
.env.production
*.key
*.pem
secrets/Regular Key Rotation
Rotate API keys every 90 days:
// Automated key rotation reminder
const KEY_ROTATION_DAYS = 90;
const lastRotation = new Date('2024-01-01');
const daysSince = (Date.now() - lastRotation) / (1000 * 60 * 60 * 24);
if (daysSince > KEY_ROTATION_DAYS) {
console.warn('⚠ API keys should be rotated!');
}API Key Security
Server-Side Only
API keys must only be used server-side:
// Good – server-side only (e.g. Node.js + Express)
const express = require('express');
const app = express();
app.post('/create-campaign', express.json(), async (req, res) => {
const r = await fetch('https://api.ugc.africa/automation/actions/campaigns', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.UGSK_LIVE}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(req.body),
});
const json = await r.json();
res.status(r.status).json(json);
});
// ❌ Bad – never call the API with your API key from the browserOne key per integration
Create a separate key per service or environment in Developer Console so you can rotate or revoke one integration's access without affecting the others:
const campaignsKey = process.env.UGSK_CAMPAIGNS;
await fetch('https://api.ugc.africa/automation/actions/campaigns', {
headers: { Authorization: `Bearer ${campaignsKey}` },
});Monitor Usage
Track API key usage in your dashboard:
// Example: thin wrapper so you can audit calls to UGC
async function UGCApiFetch(url, options = {}) {
const res = await fetch(url, options);
console.log(
JSON.stringify({ url, status: res.status, at: new Date().toISOString() }),
);
return res;
}Revoke Compromised Keys
Immediately revoke keys if compromised:
Revoke or rotate keys immediately in Developer Console if you suspect exposure. Do not wait for the next deploy.
JWT Token Security
The patterns below apply when your application issues JWTs for your own users. Validate exp, iss, and aud the way your identity provider recommends. For people using UGC in the browser, see .
Short Expiration Times
Use short-lived tokens (1 hour):
// Token expires in 1 hour
const token = jwt.sign(payload, secret, { expiresIn: '1h' });Secure Storage
Store JWT tokens securely:
// Good - httpOnly cookie (server-side)
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000 // 1 hour
});
// Good - Secure storage (mobile)
await SecureStore.setItemAsync('UGC_token', token);
// ❌ Bad - localStorage (vulnerable to XSS)
localStorage.setItem('token', token);Implement Token Refresh
Always implement token refresh:
let accessToken = null;
let refreshToken = null;
async function makeRequest(url) {
let res = await fetch(url, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (res.status === 401 && refreshToken) {
// Pseudocode: exchange refresh for new access token (your auth server / IdP)
const refreshed = await fetch('https://your-idp.example/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: refreshToken }),
});
const body = await refreshed.json();
accessToken = body.access_token;
refreshToken = body.refresh_token ?? refreshToken;
res = await fetch(url, {
headers: { Authorization: `Bearer ${accessToken}` },
});
}
return res;
}Validate Tokens
Always validate JWT tokens:
const jwt = require('jsonwebtoken');
function validateToken(token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Check expiration
if (decoded.exp < Date.now() / 1000) {
throw new Error('Token expired');
}
// Check issuer (use your service’s expected `iss`, or what UGC documents for a given integration)
if (decoded.iss !== 'https://your-idp.example') {
throw new Error('Invalid issuer');
}
return decoded;
} catch (error) {
console.error('Token validation failed:', error);
return null;
}
}Webhook Security
Verify Signatures
Always verify webhook signatures:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(payload).digest('hex');
// Constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}
app.post('/webhooks/UGC', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-ugc-signature'];
const isValid = verifyWebhookSignature(
req.body,
signature,
process.env.UGC_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process webhook
const event = JSON.parse(req.body);
handleWebhookEvent(event);
res.status(200).send('OK');
});Use HTTPS for Webhooks
Always use HTTPS URLs for webhook endpoints:
# Good
https://api.yoursite.com/webhooks/UGC
# ❌ Bad
http://api.yoursite.com/webhooks/UGCImplement Idempotency
Handle duplicate webhooks:
const processedEvents = new Set();
function handleWebhookEvent(event) {
// Check if already processed
if (processedEvents.has(event.id)) {
console.log('Event already processed:', event.id);
return;
}
// Process event (see /api/automation/triggers for the full trigger catalog)
switch (event.trigger) {
case 'campaign_funded':
handleCampaignFunded(event.data);
break;
case 'earnings_released':
handleEarningsReleased(event.data);
break;
}
// Mark as processed
processedEvents.add(event.id);
}Rate Limiting
Implement rate limiting to prevent abuse:
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later'
});
app.use('/api/', apiLimiter);Data Encryption
Encrypt Sensitive Data
Encrypt sensitive data at rest:
const crypto = require('crypto');
function encrypt(text, key) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
function decrypt(text, key) {
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(key), iv);
let decrypted = decipher.update(encrypted);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
// Encrypt customer data
const encryptedEmail = encrypt(customerEmail, process.env.ENCRYPTION_KEY);Use TLS 1.2+
Ensure your server uses TLS 1.2 or higher:
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem'),
minVersion: 'TLSv1.2'
};
https.createServer(options, app).listen(443);Logging and Monitoring
Log Security Events
Log all authentication attempts:
function logSecurityEvent(event) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
type: event.type,
userId: event.userId,
ipAddress: event.ipAddress,
userAgent: event.userAgent,
success: event.success
}));
}
// Example only: authenticate against YOUR identity layer, then log
app.post('/auth/login', async (req, res) => {
try {
const session = await yourAuthService.authenticate(req.body);
logSecurityEvent({
type: 'login',
userId: session.user.id,
ipAddress: req.ip,
userAgent: req.get('user-agent'),
success: true
});
res.json(session);
} catch (error) {
logSecurityEvent({
type: 'login_failed',
userId: req.body.email,
ipAddress: req.ip,
userAgent: req.get('user-agent'),
success: false
});
res.status(401).json({ error: 'Invalid credentials' });
}
});Monitor for Suspicious Activity
Set up alerts for unusual patterns:
const suspiciousPatterns = {
maxFailedLogins: 5,
maxRequestsPerMinute: 100,
unusualLocations: ['CN', 'RU', 'KP'] // Example
};
function checkSuspiciousActivity(userId, activity) {
if (activity.failedLogins > suspiciousPatterns.maxFailedLogins) {
alert(`User ${userId} has ${activity.failedLogins} failed logins`);
lockAccount(userId);
}
if (activity.requestsPerMinute > suspiciousPatterns.maxRequestsPerMinute) {
alert(`User ${userId} is making ${activity.requestsPerMinute} requests/min`);
temporarilyBlockUser(userId);
}
}Compliance
GDPR Compliance
Implement data protection measures:
// Data retention policy
const DATA_RETENTION_DAYS = 90;
async function deleteOldData() {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - DATA_RETENTION_DAYS);
await db.logs.deleteMany({
createdAt: { $lt: cutoffDate }
});
}
// Right to be forgotten
async function deleteUserData(userId) {
await db.users.deleteOne({ id: userId });
await db.transactions.updateMany(
{ userId },
{ $set: { userId: 'deleted', email: '[email protected]' } }
);
}PCI DSS Compliance
Never store sensitive card data:
// ❌ Never do this
const unsafe = {
cardNumber: '4111111111111111',
cvv: '123',
expiryDate: '12/25'
};
// ✅ Fund campaigns server-side; card data is entered on the hosted checkout page, never on your server
await fetch('https://api.ugc.africa/automation/actions/campaigns/cmp_123/fund', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.UGSK_LIVE}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ method: 'checkout', amount: 200 }),
});Security Checklist
Before going live, ensure you've:
- Moved all API keys to environment variables
- Enabled HTTPS on all endpoints
- Implemented webhook signature verification
- Set up proper error handling (no sensitive data in errors)
- Configured rate limiting
- Implemented logging and monitoring
- Set up automated key rotation reminders
- Configured secure cookie settings
- Enabled CSRF protection
- Implemented token refresh logic
- Set up security alerts
- Reviewed and tested authentication flows
- Configured proper CORS policies
- Implemented account lockout after failed logins
- Set up regular security audits
Resources
Incident Response
If you suspect a security breach:
1. Immediately revoke all potentially compromised API keys via the Developer Console
2. Rotate all credentials
3. Notify UGC security at [email protected]
4. Investigate access logs for unauthorized activity
5. Document the incident
6. Update security measures to prevent recurrence
Contact the security team: [email protected]
Did this solve your problem?