Skip to main content

Security Overview

Quick links: AuthenticationAuthorizationSecrets ManagementHardening ChecklistSecurity Boundaries

Security Principles

Soku follows defense in depth with multiple security layers:

  1. 🔐 Authentication - Multi-factor identity verification (Firebase Auth, API Keys, Webhooks)
  2. 🛡️ Authorization - Role-based access control (Firestore Rules, Express middleware)
  3. 🔒 Secrets Management - Environment-based configuration (no hardcoded credentials)
  4. 🌐 Network Security - HTTPS-only, CORS restrictions, OIDC tokens
  5. 📊 Monitoring - Auth anomalies, quota spikes, retry patterns
  6. 🔄 Supply Chain - Locked dependencies, vulnerability scanning, provenance


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 __session cookie (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 (revokedAt timestamp)
  • ✅ User attribution (userId in key document)
  • ✅ Subscription requirement check

Read more: API Key LifecyclePublic 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 ConventionsMiddleware 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 & ConfigurationEnvironment 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 ActionsMonitoring


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 StrategyCI/CD Diagram


Monitoring & Alerting

Security Events to Monitor

EventThresholdAction
Failed login attempts>5 from same IP in 5 minRate limit IP, alert
API key authentication failures>10 in 1 hourReview key, potential revocation
Firestore quota exceeded>80% of quotaScale up or optimize queries
Cloud Task retry spikes>50 retries on single taskInvestigate function errors
Unusual API usage patterns10x normal request volumeCheck for abuse, adjust rate limits
Webhook signature failures>3 failuresVerify 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: ObservabilityMonitoring


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