aiohttp-cors 0.8.1
Overview
aiohttp_cors implements Cross-Origin Resource Sharing (CORS) support for aiohttp's asyncio-powered HTTP server. It handles both preflight (OPTIONS) requests and response header injection for actual cross-origin requests, following the W3C CORS specification.
The library is configured per-application via aiohttp_cors.setup(), which returns a CorsConfig instance. Routes are then explicitly added to CORS configuration with origin-to-options mappings. Each route can have its own CORS policy, or inherit from application-level defaults.
Version 0.8.1 requires Python 3.9+ and aiohttp 3.9+. It is licensed under Apache 2.0.
When to Use
- Building aiohttp.web APIs that need to be accessed by browsers from different origins
- Configuring per-route or global CORS policies with specific origin allowlists
- Handling CORS preflight requests automatically with proper
OPTIONSresponses - Enabling credential passing (cookies, HTTP auth) for cross-origin requests
- Exposing custom server headers to cross-origin clients
- Working with aiohttp
web.Viewsubclasses that need CORS support
Core Concepts
Same-Origin Policy (SOP)
Browsers enforce SOP: a page at one origin (scheme, host, port) cannot read resources from a different origin. Pages can embed resources (images, scripts, iframes) but cannot read their content. This protects against malicious pages reading authenticated data from other sites.
How CORS Works
CORS allows servers to opt-in to cross-origin access:
- Browser sends
Origin: https://client.example.comheader with the request - Server responds with
Access-Control-Allow-Origin: https://client.example.com - Browser checks the header and allows or denies client-side access
For non-simple requests (custom headers, methods like PUT/DELETE), the browser first sends an OPTIONS preflight request to check if the actual request is allowed. The server responds with allowed methods, headers, and caching duration.
Key Classes
CorsConfig— Application-level CORS configuration container. Created bysetup(). One instance perweb.Application.ResourceOptions— Per-origin CORS options: credentials, exposed headers, allowed headers, max age, allowed methods.CorsViewMixin— Mixin forweb.Viewsubclasses to enable CORS on view handlers.custom_cors()— Decorator to override CORS config on individual view methods.
Installation / Setup
Install via pip:
pip install aiohttp_cors
Requirements: Python 3.9+, aiohttp 3.9+.
Usage Examples
Basic Setup
Configure CORS for a single route with specific origin:
from aiohttp import web
import aiohttp_cors
async def handler(request):
return web.Response(
text="Hello!",
headers={"X-Custom-Server-Header": "Custom data"}
)
app = web.Application()
cors = aiohttp_cors.setup(app)
resource = cors.add(app.router.add_resource("/hello"))
cors.add(resource.add_route("GET", handler), {
"http://client.example.org": aiohttp_cors.ResourceOptions(
allow_credentials=True,
expose_headers=("X-Custom-Server-Header",),
allow_headers=("X-Requested-With", "Content-Type"),
max_age=3600,
)
})
Wildcard Origin (*)
Allow all origins with restricted options:
cors.add(route, {
"*": aiohttp_cors.ResourceOptions(allow_credentials=False),
"http://client.example.org": aiohttp_cors.ResourceOptions(
allow_credentials=True
),
})
Specific origins take precedence over *. Note that allow_credentials=True cannot be combined with * origin in the same options entry — use specific origins when credentials are needed.
Global Defaults
Set default CORS policy applied to all CORS-enabled routes:
cors = aiohttp_cors.setup(app, defaults={
"http://client.example.org": aiohttp_cors.ResourceOptions(),
})
# POST and PUT available only to http://client.example.org
hello_resource = cors.add(app.router.add_resource("/hello"))
cors.add(hello_resource.add_route("POST", handler_post))
cors.add(hello_resource.add_route("PUT", handler_put))
# GET additionally allowed from another origin
cors.add(hello_resource.add_route("GET", handler), {
"http://other-client.example.org": aiohttp_cors.ResourceOptions(),
})
Resource-Level Defaults with allow_methods
Avoid adding every route to CORS config by specifying allowed methods at the resource level:
hello_resource = cors.add(app.router.add_resource("/hello"), {
"http://client.example.org": aiohttp_cors.ResourceOptions(
allow_methods=["POST", "PUT"]
),
})
# POST and PUT are CORS-enabled without calling cors.add() on each route.
hello_resource.add_route("POST", handler_post)
hello_resource.add_route("PUT", handler_put)
# DELETE still needs explicit CORS registration:
cors.add(hello_resource.add_route("DELETE", handler_delete))
Enable CORS on All Routes
Bulk-enable CORS on all existing routes with global defaults:
# Setup application routes normally.
app.router.add_route("GET", "/hello", handler_get)
app.router.add_route("PUT", "/hello", handler_put)
app.router.add_route("POST", "/hello", handler_post)
app.router.add_route("DELETE", "/hello", handler_delete)
# Configure default CORS settings.
cors = aiohttp_cors.setup(app, defaults={
"*": aiohttp_cors.ResourceOptions(
allow_credentials=True,
expose_headers="*",
allow_headers="*",
)
})
# Apply CORS to all routes.
for route in list(app.router.routes()):
cors.add(route)
Wildcard Headers
Use "*" to allow or expose all headers:
cors.add(route, {
"http://client.example.org": aiohttp_cors.ResourceOptions(
expose_headers="*",
allow_headers="*",
),
})
When expose_headers="*", all non-simple response headers are automatically exposed. Simple response headers (Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma) are always accessible and don't need to be listed.
Web Views with CorsViewMixin
Enable CORS on web.View subclasses:
from aiohttp_cors import CorsViewMixin, ResourceOptions, custom_cors
class MyView(web.View, CorsViewMixin):
cors_config = {
"*": ResourceOptions(
allow_credentials=True,
allow_headers="X-Request-ID",
)
}
async def get(self):
return web.Response(text="GET response")
@custom_cors({
"*": ResourceOptions(
allow_credentials=True,
allow_headers="*",
)
})
async def post(self):
return web.Response(text="POST response")
Register the view with CORS:
cors.add(app.router.add_route("*", "/resource", MyView))
The webview=True argument to cors.add() is deprecated — views are detected automatically by type.
Configuration priority for views: method-level @custom_cors decorator > class-level cors_config > global defaults from setup(). These are merged using collections.ChainMap.
ResourceOptions Reference
ResourceOptions is a named tuple with five fields. All parameters are keyword-only.
allow_credentials (bool, default: False)
Allow passing client credentials (cookies, HTTP auth) to the resource from another origin. When True, the server responds with Access-Control-Allow-Credentials: true.
expose_headers (sequence of strings or "*", default: ())
Server headers that the client is allowed to read. Simple response headers are always accessible. Use "*" to expose all non-simple headers in the response.
allow_headers (sequence of strings or "*", default: ())
Client headers that are allowed in the actual request. Validated during preflight. Use "*" to allow any header. Header names are normalized to uppercase internally.
max_age (int or None, default: None)
How long (in seconds) the browser may cache the preflight response. Sets Access-Control-Max-Age header. Must be a non-negative integer.
allow_methods (sequence of strings or "*", default: None)
Explicitly list allowed HTTP methods for CORS. When set, routes for those methods don't need individual cors.add() calls — the resource-level config handles them. Use "*" for all methods. Method names are normalized to uppercase internally.
When allow_methods is None, CORS availability depends on which routes have been explicitly added via cors.add().
Preflight Request Handling
The library automatically handles CORS preflight (OPTIONS) requests. When a browser sends a preflight request:
- The
Originheader is validated Access-Control-Request-Methodis parsedAccess-Control-Request-Headersare checked againstallow_headers- If allowed, the server responds with:
Access-Control-Allow-Origin— matching the request originAccess-Control-Allow-Credentials— if credentials are allowedAccess-Control-Max-Age— ifmax_ageis setAccess-Control-Allow-Methods— the requested methodAccess-Control-Allow-Headers— the requested headers
If any check fails, HTTP 403 Forbidden is returned with a descriptive error message.
Non-Preflight Request Processing
For actual cross-origin requests (non-OPTIONS), the library hooks into aiohttp's on_response_prepare signal to inject CORS headers:
- Checks if the route has CORS enabled
- Reads the
Originheader from the request - Looks up matching origin or
"*"in configuration - Sets
Access-Control-Allow-Origin,Access-Control-Allow-Credentials, andAccess-Control-Expose-Headerson the response
If no Origin header is present or the origin is not configured, CORS headers are not added (same-origin request proceeds normally).
Configuration Validation
The library validates configuration at setup time:
- Origin keys must be strings
allow_credentialsmust be booleanexpose_headersandallow_headersmust be sequences of strings or"*"max_agemust be a non-negative integer orNoneallow_methodsmust be a sequence of strings,"*", orNone- Config mappings passed to
setup()defaults orcors.add()can use eitherResourceOptionsinstances or plain dicts with the same keys
Migration Notes
From 0.7.x to 0.8.x
- Python 3.9+ is now required (3.8 support dropped)
- aiohttp 3.9+ is now required
- The
webview=Trueparameter oncors.add()is deprecated — views are detected automatically by checking if the handler is a subclass of bothweb.ViewandCorsViewMixin
From 0.4.x to 0.5.x
- aiohttp 0.21.4+ required (new Resources API)
allow_methodsoption added for resource-level method controlAbstractRouterAdapterrewritten for better router agnosticism