How to Configure CORS Settings for Cross-Origin Requests in AxonHub

AxonHub enables cross-origin resource sharing (CORS) through a declarative configuration block under server.cors, which activates Gin's CORS middleware when enabled: true and validates that allowed_origins is non-empty at startup.

AxonHub is an open-source platform built on the Gin web framework that handles HTTP traffic for event-driven architectures. To configure CORS settings for cross-origin requests, administrators modify Viper-backed configuration files or environment variables that populate the CORS struct defined in the server's configuration schema.

Understanding AxonHub's CORS Architecture

AxonHub implements CORS using the gin-contrib/cors middleware. The configuration lifecycle follows three distinct stages: default value definition in conf/conf.go, runtime validation in cmd/axonhub/main.go, and middleware registration in internal/server/routes.go. This separation ensures that invalid CORS configurations fail fast during application startup rather than at runtime.

Configuration Schema and Default Values

The CORS configuration struct is initialized with secure defaults in [conf/conf.go](https://github.com/looplj/axonhub/blob/unstable/conf/conf.go#L42-L52). The relevant fields include:

  • enabled – Boolean toggle to activate the middleware (default: false).
  • debug – Enables verbose CORS logging for troubleshooting (default: false).
  • allowed_origins – Slice of permitted origin domains (default: empty).
  • allowed_methods – HTTP methods allowed for cross-origin requests (default: ["GET", "POST", "PUT", "PATCH", "DELETE"]).
  • allowed_headers – Headers browsers may send (default: ["Origin", "Content-Type", "Accept", "Authorization"]).
  • exposed_headers – Response headers exposed to the client (default: empty).
  • allow_credentials – Whether to expose cookies and authorization headers (default: true).
  • max_age – Duration string for preflight cache (default: "12h").

Enabling CORS Middleware

When the server initializes its Gin engine in [internal/server/routes.go](https://github.com/looplj/axonhub/blob/unstable/internal/server/routes.go#L59-L72), it checks server.Config.CORS.Enabled. If true, it constructs a cors.Config object mapping the configuration fields to the middleware options and registers it globally using server.Use(cors.New(corsConfig)). Additionally, an explicit server.OPTIONS("*any", corsHandler) route handles preflight requests for all endpoints.

Validation and Startup Checks

To prevent runtime misconfigurations, [cmd/axonhub/main.go](https://github.com/looplj/axonhub/blob/unstable/cmd/axonhub/main.go#L14-L16) validates that allowed_origins contains at least one entry when CORS is enabled. If validation fails, the application exits immediately with a descriptive error message, ensuring that cross-origin policies are explicitly defined before the server accepts traffic.

Practical Configuration Examples

YAML Configuration File

Create or modify config.yml in the working directory:

server:
  cors:
    enabled: true
    debug: false
    allowed_origins:
      - "https://app.example.com"
      - "https://admin.example.com"
    allowed_methods:
      - GET
      - POST
      - PUT
      - DELETE
      - OPTIONS
    allowed_headers:
      - Origin
      - Content-Type
      - Accept
      - Authorization
      - X-Request-ID
    exposed_headers:
      - X-Request-ID
    allow_credentials: true
    max_age: "24h"

Environment Variables

For containerized deployments, export variables following the Viper convention (replace dots with underscores and uppercase):

export SERVER_CORS_ENABLED=true
export SERVER_CORS_ALLOWED_ORIGINS='["https://app.example.com","http://localhost:3000"]'
export SERVER_CORS_ALLOW_CREDENTIALS=true
export SERVER_CORS_MAX_AGE="12h"
axonhub            # start the server

Programmatic Access (Go)

If you need to inspect the loaded configuration within a plugin or extension:

package main

import (
    "fmt"
    "github.com/looplj/axonhub/conf"
)

func main() {
    cfg, err := conf.Load()
    if err != nil {
        panic(err)
    }
    
    if cfg.APIServer.CORS.Enabled {
        fmt.Printf("CORS enabled for origins: %v\n", cfg.APIServer.CORS.AllowedOrigins)
    }
}

Summary

Frequently Asked Questions

What happens if I enable CORS but leave allowed_origins empty?

The application will exit immediately during startup with a validation error. The check in [cmd/axonhub/main.go](https://github.com/looplj/axonhub/blob/unstable/cmd/axonhub/main.go#L14-L16) requires at least one origin when CORS is enabled to prevent accidental open CORS policies.

Can I use wildcards in allowed_origins?

The underlying gin-contrib/cors library supports the wildcard "*" to allow any origin, but this is discouraged for production environments that use credentials. AxonHub’s configuration accepts the standard string slice, so you can include "*" as an element, but ensure allow_credentials is set to false when doing so.

How do I debug CORS issues?

Set server.cors.debug: true in your configuration file or SERVER_CORS_DEBUG=true as an environment variable. This enables verbose logging from the Gin CORS middleware, printing allowed origins, methods, and headers to the console for every preflight and actual request.

Does AxonHub support dynamic CORS configuration without restarting?

Currently, the CORS configuration is loaded once at startup via Viper and passed to the Gin engine during route initialization. Changing CORS settings requires a server restart to re-read the configuration file or environment variables. For dynamic updates, you would need to implement a custom middleware that reads from a hot-reloadable source.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →