Security Overview
Quick links: Authentication • Authorization • Secrets Management • Hardening Checklist • Security Boundaries
Security Principles
Soku follows defense in depth with multiple security layers:
- 🔐 Authentication - Multi-factor identity verification (Firebase Auth, API Keys, Webhooks)
- 🛡️ Authorization - Role-based access control (Firestore Rules, Express middleware)
- 🔒 Secrets Management - Environment-based configuration (no hardcoded credentials)
- 🌐 Network Security - HTTPS-only, CORS restrictions, OIDC tokens
- 📊 Monitoring - Auth anomalies, quota spikes, retry patterns
- 🔄 Supply Chain - Locked dependencies, vulnerability scanning, provenance
Related Documentation
- Security Boundaries Diagram - Visual security architecture
- Engineering Security Guide - Development security practices
- API Authentication - API key authentication
- Rate Limiting - DDoS protection and abuse prevention
- Secrets Configuration - Managing credentials
Authentication
Client Authentication (Web App)
Firebase Auth with server-verified session cookies:
// Client: Firebase Auth sign-in
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const idToken = await userCredential.user.getIdToken();
// Exchange for HTTP-only session cookie
await fetch('/api/auth/session', {
method: 'POST',
headers: { 'Authorization': `Bearer ${idToken}` }
});
Security Features:
- ✅ HTTP-only
__sessioncookie (XSS protection) - ✅ Server-side ID token verification (Firebase Admin SDK)
- ✅ Session expiration (5 days default)
- ✅ Automatic refresh on client
Read more: Authentication Guide
API Authentication (Public API)
API Keys with SHA-256 hashing:
# Client provides key in header
curl -H "soku-api-key: sk_live_abc123..." \
https://api.soku.com/v1/posts
Backend verification:
// Middleware: Hash and lookup in Firestore
const hash = crypto.createHash('sha256').update(apiKey).digest('hex');
const doc = await firestore.collection('apiKeys').doc(hash).get();
if (!doc.exists || doc.data().revokedAt) throw AuthError;
Security Features:
- ✅ Keys never stored in plaintext (SHA-256 hashed)
- ✅ Revocation support (
revokedAttimestamp) - ✅ User attribution (
userIdin key document) - ✅ Subscription requirement check
Read more: API Key Lifecycle • Public API Reference
Webhook Authentication
Stripe Webhooks with signature verification:
const signature = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(
req.rawBody,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
Meta Webhooks with verification token:
if (req.query['hub.verify_token'] !== VERIFY_TOKEN) {
return res.status(403).send('Forbidden');
}
Read more: Webhooks Flow
Authorization
Firestore Security Rules
Principle: Users can only access their own data.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users collection
match /users/{userId} {
allow read, update: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
allow delete: if false;
// Nested collections (subscriptions, integrations, etc.)
match /{document=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
// API keys (hash-based lookup)
match /apiKeys/{hash} {
allow read: if request.auth != null;
allow create, update, delete: if false; // Server-only
}
}
}
Read more: Firestore Data Model
Express Middleware (API Routes)
Multi-layer authorization:
// apps/functions/api.js
app.use(express.json());
app.use(corsMiddleware);
app.use(requestId);
app.use(checkSubscription); // ← Subscription status check
app.use(rateLimiter({ ... })); // ← Rate limiting by tier
app.use('/v1/posts', postsRouter); // ← Route-level auth
// Route handler
router.post('/',
authenticateApiKey, // ← API key verification
requireActiveSubscription, // ← Active subscription required
validateRequest(schemas.createPost), // ← Input validation
asyncHandler(async (req, res) => { ... })
);
Read more: API Conventions • Middleware Pipeline
Next.js Middleware (Web Routes)
Session-based route protection:
// apps/web/src/middleware.js
export async function middleware(request) {
const sessionCookie = request.cookies.get('__session');
// Protected paths require session
if (PROTECTED_PATHS.some(p => pathname.startsWith(p))) {
if (!sessionCookie) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return NextResponse.next();
}
Protected Paths:
/dashboard/*- User dashboard/settings/*- User settings/api/billing/*- Billing operations/api/api-keys/*- API key management
Read more: Web App Architecture
Secrets Management
Environment Variables
✅ DO: Store all secrets in environment variables
# apps/functions/.env (NOT committed to git)
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
FACEBOOK_APP_SECRET=abc123...
OPENAI_API_KEY=sk-proj-...
❌ DON'T: Hardcode secrets in source code
// BAD - Never do this
const apiKey = "sk_live_abc123...";
Configuration Loading:
// apps/functions/config.js
export const cfg = () => ({
stripe: {
secretKey: process.env.STRIPE_SECRET_KEY, // ✅ From env
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET
},
facebook: {
appId: process.env.FACEBOOK_APP_ID,
appSecret: process.env.FACEBOOK_APP_SECRET
}
});
Read more: Secrets & Configuration • Environment Setup
Firebase Functions Config
Option 1: Environment variables (recommended for Cloud Run)
firebase functions:config:set stripe.secret_key="sk_live_..."
Option 2: Google Secret Manager (enterprise)
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
const client = new SecretManagerServiceClient();
const [version] = await client.accessSecretVersion({
name: 'projects/PROJECT_ID/secrets/stripe-key/versions/latest'
});
const secret = version.payload.data.toString();
Network Security
HTTPS Enforcement
All traffic is HTTPS-only:
- ✅ Vercel auto-provisions SSL certificates for
mysoku.io - ✅ Firebase Functions use Google Cloud's HTTPS endpoints
- ✅ Cloud Tasks use HTTPS URLs for function invocation
- ✅ Redirects from HTTP → HTTPS (handled by platforms)
CORS Configuration
Restricted CORS for production:
// apps/functions/config.js
export const corsMiddleware = (req, res, next) => {
res.set("Access-Control-Allow-Origin", "*"); // ⚠️ TODO: Restrict to production domain
res.set("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
res.set("Access-Control-Allow-Headers", "Content-Type, Authorization, soku-api-key");
next();
};
Production Hardening:
// Restrict to known origins
const ALLOWED_ORIGINS = [
'https://mysoku.io',
'https://www.mysoku.io'
];
res.set("Access-Control-Allow-Origin",
ALLOWED_ORIGINS.includes(req.headers.origin)
? req.headers.origin
: ALLOWED_ORIGINS[0]
);
OIDC Tokens for Cloud Tasks
Signed requests for internal function invocation:
// apps/functions/orchestrators/tasks.js
await enqueueTask({
queueName: 'publish-posts',
url: functionUrl('publishFacebook'),
payload: { userId, postId },
oidcToken: { // ← Signed token
serviceAccountEmail: 'tasks@PROJECT.iam.gserviceaccount.com',
audience: functionUrl('publishFacebook')
}
});
Function verification (if enabled):
// Verify OIDC token in function handler
const authHeader = req.headers.authorization;
const token = authHeader?.replace('Bearer ', '');
const ticket = await admin.auth().verifyIdToken(token);
Hardening Checklist
Pre-Production
- Remove hardcoded credentials from
apps/functions/config.js(✅ FIXED - TikTok credentials removed) - Configure environment variables for all integrations
-
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET -
FACEBOOK_APP_ID,FACEBOOK_APP_SECRET -
INSTAGRAM_APP_ID,INSTAGRAM_APP_SECRET -
THREADS_APP_ID,THREADS_APP_SECRET -
X_CLIENT_ID,X_CLIENT_SECRET,X_CONSUMER_KEY,X_CONSUMER_SECRET -
TIKTOK_CLIENT_KEY,TIKTOK_CLIENT_SECRET -
YOUTUBE_CLIENT_ID,YOUTUBE_CLIENT_SECRET -
LINKEDIN_CLIENT_ID,LINKEDIN_CLIENT_SECRET -
OPENAI_API_KEY
-
- Restrict CORS to production origins only
- Enable Firestore Security Rules (deploy from
firestore.rules) - Configure Cloud Armor for DDoS protection (optional but recommended)
- Set up Secret Rotation for sensitive credentials (quarterly)
Post-Deployment
- Monitor authentication anomalies (failed login spikes, unusual API key usage)
- Track Firestore quota usage (alert on >80% quota consumption)
- Review Cloud Task retry patterns (high retries may indicate issues)
- Audit API key usage (revoke unused keys, rotate regularly)
- Enable vulnerability scanning in CI/CD (Snyk, Dependabot, npm audit)
- Test webhook signature verification (Stripe, Meta)
- Verify OIDC token enforcement on internal Cloud Tasks
Ongoing Maintenance
- Merge security updates within 7 days (critical vulnerabilities)
- Review dependency updates monthly (npm outdated, renovate)
- Rotate API keys quarterly (encourage users to rotate)
- Audit access logs quarterly (unusual patterns, unauthorized access attempts)
- Test disaster recovery (backup restoration, failover)
Read more: CI/CD & GitHub Actions • Monitoring
Supply Chain Security
Dependency Management
Lockfiles committed to ensure reproducible builds:
# All package-lock.json and pnpm-lock.yaml files are committed
git ls-files | grep lock
# apps/functions/package-lock.json
# apps/web/pnpm-lock.yaml
# pnpm-lock.yaml
No Git URL dependencies (vulnerable to repo takeover):
// ❌ DON'T
"dependencies": {
"my-lib": "git+https://github.com/user/repo.git"
}
// ✅ DO
"dependencies": {
"my-lib": "^1.2.3" // Use npm registry
}
npm provenance (when available):
npm publish --provenance
Vulnerability Scanning
Automated scanning in CI/CD:
# .github/workflows/security.yml
- name: Run npm audit
run: npm audit --audit-level=moderate
- name: Run Snyk test
run: npx snyk test --severity-threshold=high
Read more: Testing Strategy • CI/CD Diagram
Monitoring & Alerting
Security Events to Monitor
| Event | Threshold | Action |
|---|---|---|
| Failed login attempts | >5 from same IP in 5 min | Rate limit IP, alert |
| API key authentication failures | >10 in 1 hour | Review key, potential revocation |
| Firestore quota exceeded | >80% of quota | Scale up or optimize queries |
| Cloud Task retry spikes | >50 retries on single task | Investigate function errors |
| Unusual API usage patterns | 10x normal request volume | Check for abuse, adjust rate limits |
| Webhook signature failures | >3 failures | Verify webhook configuration |
Implementation:
// apps/functions/middleware/error-handler.js
async function logError(error, req, userId) {
await admin.firestore().collection('errorLogs').add({
timestamp: new Date().toISOString(),
error: { name: error.name, message: error.message, code: error.code },
request: { method: req.method, url: req.url, ip: req.ip },
user: { userId, keyHash: req.auth?.keyHash },
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
}
Read more: Observability • Monitoring
CSRF Protection
Current State: API uses soku-api-key header (CSRF-safe for API)
For browser-based endpoints (if needed):
// Install csurf middleware
import csurf from 'csurf';
const csrfProtection = csurf({ cookie: true });
router.post('/sensitive-action',
requireIdToken,
csrfProtection, // ← CSRF token verification
asyncHandler(async (req, res) => { ... })
);
Security Contacts
- Report vulnerabilities: security@soku.com (if applicable)
- GitHub Security Advisories: Private reporting
- General issues: GitHub Issues