Skip to the content.

Security

axion ships built-in CSRF and CORS middleware, plus sensible default headers on every response.

CSRF protection

CSRF protection is enabled by default (app.enable_csrf = True). It is implemented by the built-in CSRFMiddleware, which runs for every request:

Disable it for pure APIs or during development:

app = WebServer(debug=True)
app.enable_csrf = False

Configure the cookie and header names in settings:

# config/settings.py
CSRF_COOKIE_NAME = "csrftoken"
CSRF_HEADER_NAME = "X-CSRFToken"

Sending a token from JavaScript

Browser requests — the cookie is sent automatically; as long as Origin/Referer matches ALLOWED_HOSTS (the default ["*"] allows everything), no extra work is needed:

fetch('/api/user', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice' }),
});

Explicit header — for non-browser clients or when you need full control, read the cookie and send it back on the header:

const token = document.cookie
  .split('; ')
  .find((row) => row.startsWith('csrftoken='))
  ?.split('=')[1];

fetch('/api/user', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRFToken': token,
  },
  body: JSON.stringify({ name: 'Alice' }),
});

HTML forms

For server-rendered forms, use the csrf_field() Jinja2 global to embed the token as a hidden input. The form body is validated automatically:

<form method="post" action="/submit">
    
    <input type="text" name="name">
    <button type="submit">Submit</button>
</form>

Low-level helpers

The axion.middleware.csrf module exposes the primitives used by the middleware, useful for testing or custom flows:

Helper Description
get_token() Generate a new CSRF token
csrf_field() Return <input type="hidden"> HTML for use in templates
csrf_field_with_token(token) Same but with an explicit token value
verify_csrf(request, app=None) Validate the token for unsafe methods
set_csrf_cookie(response, token) Attach the csrftoken cookie

CORS

The built-in CORSMiddleware applies Cross-Origin Resource Sharing headers when the request’s Origin is allowed, and answers preflight OPTIONS requests with a 204.

Allowed origins default to ["*"] (allow all):

# config/settings.py
CORS_ALLOWED_ORIGINS = ["https://example.com"]
CORS_ALLOW_CREDENTIALS = True

Set them per-app at runtime:

app = WebServer(debug=True)
app.cors_origins = ["https://example.com", "http://localhost:5173"]

When a request has an allowed Origin, the response includes Access-Control-Allow-Origin, -Methods, -Headers, and -Credentials.

Default response headers

Every Response starts with:

and you can add more in a hook-object middleware (see the middleware guide):

from axion.middleware import BaseMiddleware


class SecurityHeadersMiddleware(BaseMiddleware):
    def before_request(self, request, response):
        response.set_header("X-Frame-Options", "DENY")
        response.set_header("Referrer-Policy", "strict-origin-when-cross-origin")

Host validation

Every request’s Host header is validated against ALLOWED_HOSTS before anything else runs. This blocks DNS-rebinding attacks. "*" (the default) allows any host:

# config/settings.py
ALLOWED_HOSTS = ["example.com", "www.example.com"]

Requests whose hostname (the Host header without the port) is not listed receive a 400. Set it per-app at runtime:

app = WebServer(debug=True)
app.allowed_hosts = ["example.com"]

Runtime settings

Setting Default Purpose
ALLOWED_HOSTS ["*"] Allowed host header values
SECRET_KEY "change-me" Secret used for signing (set in production!)
CORS_ALLOWED_ORIGINS ["*"] Origins allowed for CORS
CORS_ALLOW_CREDENTIALS True Send Access-Control-Allow-Credentials
CSRF_COOKIE_NAME "csrftoken" CSRF cookie name
CSRF_HEADER_NAME "X-CSRFToken" CSRF header name

Next steps