CORS protection

Cross-Origin Resource Sharing (aka CORS) is a huge topic by itself. The documentation here cannot go into enough detail about what it is. You are highly encouraged to do some research on your own to understand the security problem presented by it, and the theory behind the solutions. MDN Web Docs are a great first step.

In super brief terms, CORS protection is a framework that browsers use to facilitate how and when a web page can access information from another domain. It is extremely relevant to anyone building a single-page application. Often times your frontend might be on a domain like https://portal.myapp.com, but it needs to access the backend from https://api.myapp.com.

The implementation here is heavily inspired by sanic-cors, which is in turn based upon flask-cors. It is therefore very likely that you can achieve a near drop-in replacement of sanic-cors with sanic-ext.

Basic implementation#

As shown in the example in the auto-endpoints example, Sanic Extensions will automatically enable CORS protection without further action. But, it does not offer too much out of the box.

At a bare minimum, it is highly recommended that you set config.CORS_ORIGINS to the intended origin(s) that will be accessing the application.

from sanic import Sanic, text
from sanic_ext import Extend

app = Sanic(__name__)
app.config.CORS_ORIGINS = "http://foobar.com,http://bar.com"
Extend(app)

@app.get("/")
async def hello_world(request):
    return text("Hello, world.")
$ curl localhost:8000 -X OPTIONS -i
HTTP/1.1 204 No Content
allow: GET,HEAD,OPTIONS
access-control-allow-origin: http://foobar.com
connection: keep-alive

Configuration#

The true power of CORS protection, however, comes into play once you start configuring it. Here is a table of all of the options.

Key Type Default Description
CORS_ALLOW_HEADERS str or List[str] "*" The list of headers that will appear in access-control-allow-headers.
CORS_ALWAYS_SEND bool True When True, will always set a value for access-control-allow-origin. When False, will only set it if there is an Origin header.
CORS_AUTOMATIC_OPTIONS bool True When the incoming preflight request is received, whether to automatically set values for access-control-allow-headers, access-control-max-age, and access-control-allow-methods headers. If False these values will only be set on routes that are decorated with the @cors decorator.
CORS_EXPOSE_HEADERS str or List[str] "" Specific list of headers to be set in access-control-expose-headers header.
CORS_MAX_AGE str, int, timedelta 0 The maximum number of seconds the preflight response may be cached using the access-control-max-age header. A falsey value will cause the header to not be set.
CORS_METHODS str or List[str] "" The HTTP methods that the allowed origins can access, as set on the access-control-allow-methods header.
CORS_ORIGINS str, List[str], re.Pattern "*" The origins that are allowed to access the resource, as set on the access-control-allow-origin header.
CORS_SEND_WILDCARD bool False If True, will send the wildcard * origin instead of the origin request header.
CORS_SUPPORTS_CREDENTIALS bool False Whether to set the access-control-allow-credentials header.
CORS_VARY_HEADER bool True Whether to add vary header, when appropriate.

For the sake of brevity, where the above says List[str] any instance of a list, set, frozenset, or tuple will be acceptable. Alternatively, if the value is a str, it can be a comma delimited list.

Route level overrides#

It may sometimes be necessary to override app-wide settings for a specific route. To allow for this, you can use the @sanic_ext.cors() decorator to set different route-specific values.

The values that can be overridden with this decorator are:

  • origins
  • expose_headers
  • allow_headers
  • allow_methods
  • supports_credentials
  • max_age
from sanic_ext import cors

app.config.CORS_ORIGINS = "https://foo.com"

@app.get("/", host="bar.com")
@cors(origins="https://bar.com")
async def hello_world(request):
    return text("Hello, world.")