CORS Configuration¶
TornadoAPI Guard provides comprehensive CORS (Cross-Origin Resource Sharing) configuration through the SecurityConfig model. Unlike frameworks that ship with a standalone CORS middleware, the Tornado adapter applies CORS headers directly from the handler pipeline: whenever a request carries an Origin header and enable_cors=True is set on the config, SecurityHandler.apply_pre_flight_headers() calls security_headers_manager.get_cors_headers(origin) and writes the resulting headers onto the response.
The generated headers include:
Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Allow-CredentialsAccess-Control-Expose-HeadersAccess-Control-Max-Age
Each of these is derived from the matching field on SecurityConfig (cors_allow_origins, cors_allow_methods, cors_allow_headers, cors_allow_credentials, cors_expose_headers, cors_max_age).
Because the Tornado middleware has no app.add_middleware(...) equivalent, there is no SecurityMiddleware.configure_cors(app, config) helper — setting the fields on SecurityConfig is all you need.
Basic CORS Setup¶
Enable CORS with permissive defaults:
from tornadoapi_guard import SecurityConfig
config = SecurityConfig(
enable_cors=True,
cors_allow_origins=["*"],
)
Advanced Configuration¶
Configure specific CORS settings:
config = SecurityConfig(
enable_cors=True,
cors_allow_origins=[
"https://example.com",
"https://api.example.com",
],
cors_allow_methods=["GET", "POST", "PUT", "DELETE"],
cors_allow_headers=["*"],
cors_allow_credentials=True,
cors_expose_headers=["X-Custom-Header"],
cors_max_age=600,
)
Origin Patterns¶
Use patterns to match multiple origins:
config = SecurityConfig(
enable_cors=True,
cors_allow_origins=[
"https://*.example.com",
"https://*.api.example.com",
],
)
Credentials Support¶
Enable credentials support for authenticated requests:
config = SecurityConfig(
enable_cors=True,
cors_allow_credentials=True,
cors_allow_origins=[
"https://app.example.com", # Must be specific origin when using credentials
],
)
Custom Headers¶
Configure custom headers for CORS:
config = SecurityConfig(
enable_cors=True,
cors_allow_headers=[
"Authorization",
"Content-Type",
"X-Custom-Header",
],
cors_expose_headers=[
"X-Custom-Response-Header",
],
)
Preflight (OPTIONS) Requests¶
Tornado's RequestHandler.options() returns an empty 200 OK by default. When a preflight arrives, SecurityHandler.prepare() runs first and the CORS headers derived from your config are already attached — which is enough for most use cases.
If you need custom preflight behavior (for example, to return extra headers or a specific body on OPTIONS), override options() on your SecurityHandler subclass:
from tornadoapi_guard import SecurityHandler
class APIHandler(SecurityHandler):
async def options(self, *args, **kwargs) -> None:
self.set_status(204)
self.finish()
async def get(self) -> None:
self.write({"data": "value"})
The CORS headers configured via SecurityConfig are still applied automatically — you only need to override options() when the default behavior is insufficient.
Troubleshooting¶
Common issues:
- Missing CORS headers: Ensure
enable_cors=Trueis set and the request actually carries anOriginheader. Requests without anOriginheader do not receive CORS headers. - Credentials + wildcard origin: Browsers reject
Access-Control-Allow-Origin: *when credentials are enabled. Use an explicit origin list whencors_allow_credentials=True. - Preflight failing: Verify that the methods and headers your client sends are included in
cors_allow_methodsandcors_allow_headers. - Security headers disabled: CORS headers are emitted by the security headers manager. If
security_headers={"enabled": False}is set on the config, CORS headers will not be written either.