From 95bf057104661465e73cf23462f8db3be2db75e6 Mon Sep 17 00:00:00 2001 From: Roberto Prevato Date: Mon, 23 Jun 2025 18:44:17 +0200 Subject: [PATCH] Improve documentation for sessions and serving files --- blacksheep/docs/sessions.md | 32 +++++++++++++++++- blacksheep/docs/static-files.md | 57 ++++++++++++++++++++++++++++++--- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/blacksheep/docs/sessions.md b/blacksheep/docs/sessions.md index 5546025..972f41e 100644 --- a/blacksheep/docs/sessions.md +++ b/blacksheep/docs/sessions.md @@ -67,7 +67,7 @@ sequenceDiagram Client->>Server: HTTP Request (Cookie: session ID) Server->>Store: Retrieve session data using session ID Store-->>Server: Session data - Server-->>Client: HTTP Response (may update Set-Cookie) + Server-->>Client: HTTP Response ``` In this scenario, Session ID is exchanged between client and server via @@ -137,6 +137,36 @@ Before version `2.4.0`, BlackSheep offered built-in support only for sessions stored entirely on the client side, in cookies. Using sessions with a different store required custom code. +/// admonition | Cookies and cross-site request forgery. + type: danger + +When you store sessions in cookies on the client side, web applications must +implement [Anti-Forgery validation](anti-request-forgery.md) to prevent +Cross-Site Request Forgery (XSRF/CSRF). + +**Why?** + +Cookies are sent automatically by browsers with every request to your +domain, making your app vulnerable to Cross-Site Request Forgery (CSRF) +attacks, if information in cookies is used to authenticate requests and your +application does not implement [Anti-Forgery +validation](anti-request-forgery.md). + +**How?** + +Common anti-forgery mechanisms include: + +- CSRF tokens: Generate a unique token per session/request and require it in + forms or headers. +- SameSite cookies: Set your session cookie with `SameSite=Strict` or + `SameSite=Lax` to limit cross-site requests. +- Custom headers: For APIs, use and require a custom header (e.g., + `X-Session-ID`) that browsers do not send cross-origin. In a user-defined + `SessionStore` type, you can rely on a custom request and response header to + exchange information with clients —updating your client code accordingly. + +/// + ## Enabling sessions, with custom store Starting from `2.4.0`, the built-in classes for sessions include support for diff --git a/blacksheep/docs/static-files.md b/blacksheep/docs/static-files.md index ce1a8eb..233c5ca 100644 --- a/blacksheep/docs/static-files.md +++ b/blacksheep/docs/static-files.md @@ -1,4 +1,4 @@ -# Serving static files +F# Serving static files This page covers: @@ -28,21 +28,22 @@ When serving files this way, a match-all route ("*") is configured in the application router for `GET` and `HEAD`, and files are read from the configured folder upon web requests. + It is also possible to serve static files from sub-folders: ```python app.serve_files("app/static") ``` -Enable file discovery (in such case, requests for directories will generate an -HTML response with a list of files): +To enable file discovery, add the `discovery=True` parameter. In such case, +requests for directories will generate an HTML response with a list of files: ```python app.serve_files("app/static", discovery=True) ``` -BlackSheep also supports serving static files from multiple folders, and -specifying a prefix for the route path: +Serving static files from multiple folders is supported, using a different path +for each folder: ```python app = Application() @@ -52,6 +53,52 @@ app.serve_files("app/images", root_path="images") app.serve_files("app/videos", root_path="videos") ``` +### Route paths and folder names + +By default, when you serve files from a folder, the folder name is **not** +included in the route at which files are served. For example, if you have a +file `test.txt` in your `static` directory and you use: + +```python +app.serve_files("static") +``` + +the file will be accessible at `http://localhost/test.txt` (not +`http://localhost/static/test.txt`). + +If you want the files to be accessible under a specific route path (such as +`/static/`), use the `root_path` parameter: + +```python +app.serve_files("static", root_path="static") +``` + +With this configuration, `test.txt` will be accessible at `http://localhost/static/test.txt`. + +### Serving single files + +If you need to serve single files, it is possible to use the `file` function +like in the following example. + +```python +from blacksheep import Application, get +from blacksheep.server.responses import file, ContentDispositionType + + +app = Application() + + +@get("/favicon.ico") +def favicon_icon(): + # Note: the file is read asynchronously and served in chunks, + # even though this function itself is synchronous + return file( + "path-to-your/favicon.ico", # ← local path + "image/vnd.microsoft.icon", + content_disposition=ContentDispositionType.INLINE, + ) +``` + ## File extensions Only files with a configured extension are served to the client. By default,