| name | aiohttp-cors-0-8-1 |
| description | Implement Cross-Origin Resource Sharing (CORS) in aiohttp applications using aiohttp-cors 0.8.1, enabling secure cross-origin requests with configurable origins, credentials, headers, and preflight caching. Use when building aiohttp.web APIs serving browsers from different origins, configuring per-route or global CORS policies, or working with web views requiring CORS. |
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
OPTIONS responses
- Enabling credential passing (cookies, HTTP auth) for cross-origin requests
- Exposing custom server headers to cross-origin clients
- Working with aiohttp
web.View subclasses 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.com header 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 by setup(). One instance per web.Application.
ResourceOptions — Per-origin CORS options: credentials, exposed headers, allowed headers, max age, allowed methods.
CorsViewMixin — Mixin for web.View subclasses 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(),
})
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))
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"]
),
})
hello_resource.add_route("POST", handler_post)
hello_resource.add_route("PUT", handler_put)
cors.add(hello_resource.add_route("DELETE", handler_delete))
Enable CORS on All Routes
Bulk-enable CORS on all existing routes with global defaults:
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)
cors = aiohttp_cors.setup(app, defaults={
"*": aiohttp_cors.ResourceOptions(
allow_credentials=True,
expose_headers="*",
allow_headers="*",
)
})
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
Origin header is validated
Access-Control-Request-Method is parsed
Access-Control-Request-Headers are checked against allow_headers
- If allowed, the server responds with:
Access-Control-Allow-Origin — matching the request origin
Access-Control-Allow-Credentials — if credentials are allowed
Access-Control-Max-Age — if max_age is set
Access-Control-Allow-Methods — the requested method
Access-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
Origin header from the request
- Looks up matching origin or
"*" in configuration
- Sets
Access-Control-Allow-Origin, Access-Control-Allow-Credentials, and Access-Control-Expose-Headers on 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_credentials must be boolean
expose_headers and allow_headers must be sequences of strings or "*"
max_age must be a non-negative integer or None
allow_methods must be a sequence of strings, "*", or None
- Config mappings passed to
setup() defaults or cors.add() can use either ResourceOptions instances 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=True parameter on cors.add() is deprecated — views are detected automatically by checking if the handler is a subclass of both web.View and CorsViewMixin
From 0.4.x to 0.5.x
- aiohttp 0.21.4+ required (new Resources API)
allow_methods option added for resource-level method control
AbstractRouterAdapter rewritten for better router agnosticism