Skip to content

Serving blueprint

In the production environment we will use the backend to serve the frontend (in development we used the frontend to serve itself and proxy requests to the backend).

Creating the blueprint

The blueprint itself can be created with the following code in backend/src/backend/blueprints/serving.py,

from quart import Blueprint

blueprint = Blueprint("serving", __name__)

and activated by adding the following to backend/src/backend/run.py,

from backend.blueprints.serving import blueprint as serving_blueprint

def create_app() -> Quart:
    ...
    app.register_blueprint(serving_blueprint)

Serving static assets

Quart will by default serve any assets placed in the backend/src/backend/static/ (app.root_path / static) directory. So there is nothing for us to do here other than ensure that static assets are placed in this directory.

Serving pages

As the frontend routes paths to the displayed page the backend has no knowledge which paths are valid. For this reason the route handler should serve any path and let the frontend decide if it is valid or not. This is done in quart using a <path:path> url variable.

In addition the served page should be as secure as we can make it using secure headers, such as a Content Security Policy, and various others. The following should be added to backend/src/backend/blueprints/serving.py,

from typing import Optional

from quart import make_response, render_template, ResponseReturnValue
from quart_rate_limiter import rate_exempt
from werkzeug.http import COEP, COOP

@blueprint.route("/")
@blueprint.route("/<path:path>")
@rate_exempt
async def index(path: Optional[str] = None) -> ResponseReturnValue:
    response = await make_response(await render_template("index.html"))
    response.headers["Content-Security-Policy"] = ""
    response.content_security_policy.default_src = "'self'"
    response.content_security_policy.base_uri = "'self'"
    response.content_security_policy.font_src = "'self' data:"
    response.content_security_policy.form_action = "'self'"
    response.content_security_policy.frame_ancestors = "'none'"
    response.content_security_policy.img_src = "'self' data:"
    response.content_security_policy.style_src = "'self' 'unsafe-inline'"
    response.cross_origin_embedder_policy = COEP.REQUIRE_CORP
    response.cross_origin_opener_policy = COOP.SAME_ORIGIN
    response.headers["Referrer-Policy"] = "no-referrer, strict-origin-when-cross-origin"
    response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains; preload"
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "SAMEORIGIN"
    response.headers["X-XSS-Protection"] = "1; mode=block"
    return response

Note

The frontend build creates an index.html which should be placed in the backend/src/backend/templates folder and creates static files that should be placed in the backend/src/backend/static folder for the above to work. See the Docker section for more.