Skip to content

traefik middleware, subdomain cors config

License

x-ream/cors-dynamic-subdomain

Repository files navigation

CORS Dynamic Subdomain Plugin for Traefik

Go Report Card

A Traefik middleware plugin that dynamically sets CORS headers to support wildcard subdomain matching.

Why This Plugin?

Traefik's built-in CORS middleware doesn't support dynamic Access-Control-Allow-Origin headers. When you configure Traefik's built-in CORS:

# ❌ Traefik's built-in CORS (doesn't work for wildcard subdomains)
http:
  middlewares:
    cors:
      headers:
        accessControlAllowOriginList:
          - "https://*.fndo.me"

Traefik returns the literal string https://*.fndo.me in the response header, which browsers reject because CORS spec requires either:

  • An exact origin: https://bigrob.fndo.me
  • A wildcard: *
  • NOT a pattern: https://*.fndo.me

This plugin solves this by:

  1. Reading the Origin header from the request
  2. Matching it against your wildcard patterns
  3. Returning the exact origin in Access-Control-Allow-Origin

Use Case: Shopify-like Multi-Tenant Architecture

Perfect for SaaS platforms where each tenant has their own subdomain:

  • https://shop1.example.com
  • https://shop2.example.com
  • https://*.example.com (unlimited shops)

All subdomains can call your API at https://api.example.com with proper CORS.

Features

  • ✅ Wildcard subdomain matching (https://*.example.com)
  • ✅ Multiple pattern support
  • ✅ Exact domain matching (https://example.com)
  • ✅ Port-aware matching (https://*.example.com:8080)
  • ✅ Credentials support (optional)
  • ✅ Preflight request handling
  • ✅ Configurable methods, headers, max-age

Installation

Static Configuration

Add the plugin to your Traefik static configuration:

traefik.yml

experimental:
  plugins:
    cors-dynamic-subdomain:
      moduleName: "github.com/x-ream/cors-dynamic-subdomain"
      version: "v0.1.0"

Or via command line:

--experimental.plugins.cors-dynamic-subdomain.modulename=github.com/x-ream/cors-dynamic-subdomain
--experimental.plugins.cors-dynamic-subdomain.version=v0.1.0

Dynamic Configuration

Configure the middleware in your dynamic configuration:

Kubernetes CRD:

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: cors-dynamic
spec:
  plugin:
    cors-dynamic-subdomain:
      # ✅ This plugin uses 'allowedOriginPatterns' (not accessControlAllowOriginList)
      allowedOriginPatterns:
        - "https://*.fndo.me"
        - "https://fndo.me"
        - "https://api.fndo.me"
      allowedMethods:
        - GET
        - POST
        - PUT
        - DELETE
        - OPTIONS
        - PATCH
      allowedHeaders:
        - "*"
      allowCredentials: false
      maxAge: 86400

Apply to IngressRoute:

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: api
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`api.fndo.me`)
      kind: Rule
      services:
        - name: api-service
          port: 8080
      middlewares:
        - name: cors-dynamic

Docker Compose Labels:

services:
  api:
    image: myapi:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`api.example.com`)"
      - "traefik.http.routers.api.middlewares=cors-dynamic"
      - "traefik.http.middlewares.cors-dynamic.plugin.cors-dynamic-subdomain.allowedOriginPatterns=https://*.example.com,https://example.com"
      - "traefik.http.middlewares.cors-dynamic.plugin.cors-dynamic-subdomain.allowedMethods=GET,POST,PUT,DELETE,OPTIONS"
      - "traefik.http.middlewares.cors-dynamic.plugin.cors-dynamic-subdomain.allowCredentials=false"

Configuration Options

📝 Important: This plugin uses different parameter names than Traefik's built-in CORS:

Traefik Built-in CORS This Plugin Purpose
accessControlAllowOriginList allowedOriginPatterns Origin matching (this plugin supports wildcards!)
accessControlAllowMethods allowedMethods HTTP methods
accessControlAllowHeaders allowedHeaders Request headers
accessControlAllowCredentials allowCredentials Cookie/auth support

All Configuration Options

Option Type Default Description
allowedOriginPatterns []string (required) List of origin patterns. Use * for wildcard subdomain matching.
allowedMethods []string ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"] HTTP methods to allow
allowedHeaders []string ["*"] Headers to allow. Use ["*"] for all headers.
exposedHeaders []string [] Headers to expose to the browser
allowCredentials bool false Whether to allow credentials (cookies, authorization headers)
maxAge int 86400 How long (in seconds) the preflight response can be cached

Pattern Matching Examples

allowedOriginPatterns:
  # Match all subdomains
  - "https://*.example.com"       # ✅ https://shop1.example.com
                                   # ✅ https://shop2.example.com
                                   # ❌ https://example.com (no subdomain)

  # Match exact domain
  - "https://example.com"          # ✅ https://example.com
                                   # ❌ https://www.example.com

  # Match with port
  - "https://*.example.com:8080"   # ✅ https://shop.example.com:8080
                                   # ❌ https://shop.example.com

  # Multiple TLDs
  - "https://*.example.com"
  - "https://*.example.io"
  - "https://*.example.dev"

How It Works

  1. Request arrives with Origin: https://shop1.example.com
  2. Plugin checks if origin matches any pattern in allowedOriginPatterns
  3. If match found, set response header: Access-Control-Allow-Origin: https://shop1.example.com
  4. Browser accepts because it received its exact origin back

Example Flow

Request:
┌─────────────────────────────────────┐
│ GET /api/products                   │
│ Host: api.example.com               │
│ Origin: https://shop1.example.com   │ ← Browser sends origin
└─────────────────────────────────────┘
        ↓
    [Traefik + Plugin]
        ↓ Matches pattern: https://*.example.com
        ↓
Response:
┌─────────────────────────────────────────────────────────┐
│ Access-Control-Allow-Origin: https://shop1.example.com │ ← Exact origin
│ Access-Control-Allow-Methods: GET, POST, ...           │
│ Access-Control-Allow-Headers: *                        │
│ Vary: Origin                                           │
└─────────────────────────────────────────────────────────┘

Credentials (Cookies/Auth Headers)

If you need to send cookies or authorization headers:

allowCredentials: true

⚠️ Important: When allowCredentials: true, you cannot use * in allowedOriginPatterns. All origins must be explicitly listed or use specific wildcard patterns.

Frontend code:

fetch('https://api.example.com/data', {
    credentials: 'include',  // ← Required for cookies
    headers: {
        'Authorization': 'Bearer token'
    }
});

Testing

Run Tests

cd cors-dynamic-subdomain
go test -v

Manual Testing

# Test subdomain
curl -i https://api.fndo.me/test \
  -H "Origin: https://bigrob.fndo.me"

# Should return:
# Access-Control-Allow-Origin: https://bigrob.fndo.me

# Test preflight
curl -i https://api.fndo.me/test \
  -X OPTIONS \
  -H "Origin: https://shop.fndo.me" \
  -H "Access-Control-Request-Method: POST"

Comparison with Traefik's Built-in CORS

Feature Built-in CORS This Plugin
Exact origin matching
Wildcard *
Subdomain wildcard *.example.com ❌ Returns literal string ✅ Returns exact origin
Multiple patterns
Dynamic origin response
Configuration key accessControlAllowOriginList allowedOriginPatterns

Common Issues

1. CORS error still occurs

Check:

  • Origin matches pattern exactly (including protocol and port)
  • Plugin is applied to the correct route
  • No other middleware is removing CORS headers

2. Credentials not working

Ensure:

allowCredentials: true

And frontend uses:

credentials: 'include'

3. Wildcard not matching

Pattern: https://*.example.com

  • ✅ Matches: https://shop.example.com
  • ❌ Does NOT match: https://example.com (no subdomain)
  • ❌ Does NOT match: http://shop.example.com (different protocol)

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new features
  4. Submit a pull request

License

MIT License - see LICENSE file

Acknowledgments

Built for developers who need Shopify-like multi-tenant architectures with Traefik.

Author

Created by x-ream

Links

About

traefik middleware, subdomain cors config

Resources

License

Stars

Watchers

Forks

Packages

No packages published