Dynamic Origin Whitelist — Building a Production-Grade CORS Origin Validator
In this tutorial, you will learn about Dynamic Origin Whitelist. We cover key concepts, practical examples, and best practices to help you master this topic.
A dynamic origin whitelist system stores allowed origins in a database or config service, validates incoming requests against the whitelist, and sets the correct Access-Control-Allow-Origin header with proper Caching and audit logging.
What You'll Learn
- Database-backed origin whitelist architecture
- Pattern matching for subdomain and path-based origins
- Performance optimization with caching
Why It Matters
Static origin lists do not scale. Enterprise APIs serving hundreds of partners need dynamic whitelist management. DodaTech's partner API uses a Redis-backed dynamic whitelist that updates in real time as new partner domains are registered.
flowchart LR
A["Request"] --> B["CORS Middleware"]
B --> C["Redis cache check"]
C -->|"Cache hit"| D["Validate origin"]
C -->|"Cache miss"| E["Query database"]
E --> F["Update Redis cache"]
F --> D
D -->|"Allowed"| G["Set ACAO + Vary: Origin"]
D -->|"Not allowed"| H["Log + reject"]
style G fill:#86efac,stroke:#16a34a
style H fill:#fecaca,stroke:#dc2626
Code Examples
# Python dynamic whitelist service
import redis
import re
class OriginWhitelist:
def __init__(self, redis_client):
self.redis = redis_client
self.cache_ttl = 300 # 5 minutes
def is_origin_allowed(self, origin):
if not origin:
return False
cached = self.redis.get(f"origin:{origin}")
if cached is not None:
return cached == b"1"
allowed = self._check_database(origin)
self.redis.setex(
f"origin:{origin}", self.cache_ttl,
b"1" if allowed else b"0"
)
return allowed
def _check_database(self, origin):
patterns = [
r"^https://[a-z0-9-]+\.example\.com$",
r"^https://(www\.)?app\.example\.com$",
r"^https://[a-z0-9-]+--partner\.web\.app$"
]
return any(re.match(p, origin) for p in patterns)
whitelist = OriginWhitelist(redis.Redis())
@app.after_request
def dynamic_origin(response):
origin = request.headers.get('Origin')
if origin and whitelist.is_origin_allowed(origin):
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Vary'] = 'Origin'
return response
// Node.js dynamic whitelist with MongoDB
const mongoose = require('mongoose');
const originSchema = new mongoose.Schema({
pattern: { type: String, required: true },
type: { type: String, enum: ['exact', 'regex'], default: 'exact' },
createdAt: { type: Date, default: Date.now }
});
const AllowedOrigin = mongoose.model('AllowedOrigin', originSchema);
async function validateOrigin(origin) {
const allowedOrigins = await AllowedOrigin.find();
return allowedOrigins.some(entry => {
if (entry.type === 'exact') return entry.pattern === origin;
if (entry.type === 'regex') return new RegExp(entry.pattern).test(origin);
return false;
});
}
app.use(cors({
origin: async (origin, callback) => {
if (!origin) return callback(null, true);
const allowed = await validateOrigin(origin);
callback(null, allowed ? origin : false);
},
credentials: true
}));
# Add an origin to the whitelist via API
curl -X POST https://admin.example.com/api/origins \
-H "Content-Type: application/json" \
-d '{"pattern": "https://myapp.example.com", "type": "exact"}'
# Test if an origin is whitelisted
curl https://admin.example.com/api/origins/check \
-H "Content-Type: application/json" \
-d '{"origin": "https://myapp.example.com"}'
Common Mistakes
1. Using Overly Broad Regex Patterns
A pattern like .*.example.com also matches evil.example.com. Be specific.
2. Not Caching Database Results
Every request hitting the database adds latency and load. Use Redis or in-memory cache.
3. Ignoring Origin Header Absence
Some valid clients do not send Origin. Decide whether to allow or block these.
4. Storing Secrets in Origin Validation
The whitelist should validate origins, not authenticate users. Do not use origin check for authorization.
5. Not Logging Rejected Origins
Blocked origins from unexpected domains may indicate attempted attacks or misconfigured clients.
Practice Questions
- What data store is recommended for dynamic origin whitelists?
- How do you cache origin validation results?
- Why should you avoid overly broad regex patterns?
- How do you update the whitelist without restarting the server?
- What should you log when an origin is rejected?
Answers:
- Redis or a database with a caching layer.
- Use Redis with a TTL or an in-memory LRU cache.
- Broad patterns may match unintended domains, including attacker-controlled domains.
- Store the whitelist in an external database or config service that is read at runtime.
- The origin, timestamp, endpoint, and a reason for the rejection.
Challenge: Implement a dynamic origin whitelist with an admin API, Redis caching, pattern matching for subdomains, real-time updates via Websocket, and a dashboard showing allowed origins, rejection rates, and cache hit ratios.
FAQ
Mini Project
Build a complete dynamic origin whitelist system with a REST API for CRUD operations on allowed origins, a Redis caching layer with configurable TTL, pattern matching for exact and regex origins, real-time cache invalidation via pub/sub, an audit log, and a dashboard showing validation metrics.
What's Next
Implement Express CORS middleware using the cors npm package, then explore Express specific route CORS for fine-grained control.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro