Skip to content

Dynamic Origin Whitelist — Building a Production-Grade CORS Origin Validator

DodaTech Updated 2026-06-28 4 min read

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

  1. What data store is recommended for dynamic origin whitelists?
  2. How do you cache origin validation results?
  3. Why should you avoid overly broad regex patterns?
  4. How do you update the whitelist without restarting the server?
  5. What should you log when an origin is rejected?

Answers:

  1. Redis or a database with a caching layer.
  2. Use Redis with a TTL or an in-memory LRU cache.
  3. Broad patterns may match unintended domains, including attacker-controlled domains.
  4. Store the whitelist in an external database or config service that is read at runtime.
  5. 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

How often should the whitelist cache be refreshed?

A TTL of 5-15 minutes balances responsiveness with database load. For near-real-time updates, use a pub/sub mechanism to invalidate cache entries when the whitelist changes.

Can I use environment variables for the whitelist?

For small, static whitelists with few origins, environment variables work. For dynamic or large whitelists, use a database or config service.

How do I handle origin changes during cache TTL?

Use a cache invalidation strategy: when the whitelist is updated, publish an event that clears affected cache entries. Redis pub/sub or a message queue works well.

Should I validate origins with exact match or regex?

Use exact matches when possible. Use regex only when you need pattern-based matching like subdomain wildcards. Always test regex patterns against known inputs.

What is the performance overhead of dynamic origin validation?

With caching, the overhead is negligible (microseconds). Without caching, each request may add 10-50ms for database lookups.

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