diff --git a/docs/web-server.md b/docs/web-server.md index 33bb5b4d..50af7115 100644 --- a/docs/web-server.md +++ b/docs/web-server.md @@ -315,6 +315,29 @@ stdout, and refuses any request that does not present it as `Authorization: Bearer `. Public paths: `/health/ping`, `/health/auth-info`, `/openapi.json`, `/docs`, `/redoc`. +### Session cookie in single-process UI mode + +With `kbagent serve --ui`, the browser never sees the bearer token: +`GET /` (and `GET /index.html`) answers the SPA shell with a +`Set-Cookie: kbagent_session=; HttpOnly; SameSite=Strict; Path=/` +session cookie, and the auth middleware accepts that cookie whenever no +`Authorization` header is present. Scripted callers keep using the header. + +Two layers keep that cookie from going stale across server restarts +*(since vNEXT)* — previously a restart (new token) could leave a tab that +reloaded from the browser cache silently 401-ing on every API call, with +each list rendering as empty: + +- The shell is served with `Cache-Control: no-cache`, so a reload always + revalidates against the server — and the bootstrap route always answers + a full `200` with a fresh `Set-Cookie`. +- The SPA's API client treats a `401` as "cookie may be stale": it + re-fetches `/` once with `cache: "reload"` (bypassing every cache + layer), retries the request, and only if the retry still answers `401` + shows a visible **Session expired** banner (for `SESSION_EXPIRED` / + `SESSION_NOT_FOUND` the banner carries the server message, which names + the on-host `kbagent auth login` remedy). + ### What's-new popup *(since vNEXT)* The web UI shows a curated per-version highlights modal on load, once per diff --git a/src/keboola_agent_cli/server/app.py b/src/keboola_agent_cli/server/app.py index f6b92e74..02060532 100644 --- a/src/keboola_agent_cli/server/app.py +++ b/src/keboola_agent_cli/server/app.py @@ -747,19 +747,26 @@ def _install_ui(app: FastAPI, *, ui_dist: str, token: str) -> None: (auth doesn't care about path, but PUBLIC_PATHS exact-matches do). 2) **Cookie-setting** ``GET /`` and ``GET /index.html``: read the built ``index.html``, return it with a ``Set-Cookie: kbagent_session=; - HttpOnly; SameSite=Strict; Path=/`` header. Public (no auth) so the - SPA can bootstrap. The browser then attaches the cookie to every - same-origin REST + SSE request automatically. The token is HttpOnly - (no JS access -- XSS-resistant), SameSite=Strict (no cross-origin - sends -- CSRF-resistant), and lives only for the browser session. + HttpOnly; SameSite=Strict; Path=/`` header and ``Cache-Control: + no-cache`` (revalidate-always -- a cached shell served without a + request would keep a stale cookie alive across server restarts). + Public (no auth) so the SPA can bootstrap. The browser then attaches + the cookie to every same-origin REST + SSE request automatically. The + token is HttpOnly (no JS access -- XSS-resistant), SameSite=Strict + (no cross-origin sends -- CSRF-resistant), and lives only for the + browser session. This replaces the older "inject ``window.__KBAGENT_TOKEN`` into a ``