From 3202dabd71fdefcd68b7c4bffe92fd79486226f4 Mon Sep 17 00:00:00 2001 From: Leszek Grzanka Date: Thu, 1 Oct 2026 19:57:58 +0200 Subject: [PATCH 1/3] Document Keycloak-only mode (ENABLE_LOCAL_USERS) Describe the ENABLE_USER_REGISTRATION and ENABLE_LOCAL_USERS backend flags, the new 403 responses on the auth endpoints, the flags reported by GET /, and how the UI hides password login when local users are disabled. Co-Authored-By: Claude Opus 5.5 --- src/content/docs/api-reference/auth.md | 10 +++++++++- src/content/docs/api-reference/overview.md | 16 +++++++++++++++- src/content/docs/architecture/auth-model.md | 16 +++++++++++++++- src/content/docs/backend/docker-deployment.md | 4 ++++ src/content/docs/backend/overview.md | 2 ++ src/content/docs/frontend/auth-flows.md | 2 ++ 6 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/content/docs/api-reference/auth.md b/src/content/docs/api-reference/auth.md index ec33323..f10874e 100644 --- a/src/content/docs/api-reference/auth.md +++ b/src/content/docs/api-reference/auth.md @@ -3,6 +3,10 @@ title: Auth Endpoints description: Authentication and session management API. --- +:::note +Native username/password auth can be disabled per instance with the `ENABLE_USER_REGISTRATION` and `ENABLE_LOCAL_USERS` environment variables. See [Keycloak-only deployments](/for_developers/architecture/auth-model/#keycloak-only-deployments). +::: + ## Register Create a new local user account. @@ -28,7 +32,7 @@ Content-Type: application/json **Errors:** - `400` — Missing username or password -- `403` — Registration is disabled on this instance +- `403` — User already exists, or registration is disabled on this instance (`ENABLE_USER_REGISTRATION` or `ENABLE_LOCAL_USERS` set to `false`) --- @@ -63,6 +67,7 @@ Content-Type: application/json **Errors:** - `401` — Invalid credentials - `400` — Missing fields +- `403` — Local users are disabled on this instance (`ENABLE_LOCAL_USERS=false`) --- @@ -89,6 +94,7 @@ Cookie: refresh_token= **Errors:** - `401` — Invalid or expired refresh token +- `403` — Local users are disabled on this instance (`ENABLE_LOCAL_USERS=false`) --- @@ -113,6 +119,8 @@ Cookie: access_token= The `source` field indicates the authentication provider: `"local"` or `"keycloak"`. +When `ENABLE_LOCAL_USERS=false`, this and every other protected endpoint return `403` for local users, even with a token issued before the switch. + --- ## Logout diff --git a/src/content/docs/api-reference/overview.md b/src/content/docs/api-reference/overview.md index a43fdb4..d9f84f0 100644 --- a/src/content/docs/api-reference/overview.md +++ b/src/content/docs/api-reference/overview.md @@ -26,6 +26,20 @@ Two authentication modes: Protected endpoints return `401 Unauthorized` if no valid token is present. +Native auth can be disabled per instance (see [Keycloak-only deployments](/for_developers/architecture/auth-model/#keycloak-only-deployments)). The root endpoint reports which methods are available: + +```http +GET / +``` + +```json +{ + "message": "Hello World!", + "local_users_enabled": true, + "registration_enabled": true +} +``` + ## Endpoint Groups | Group | Prefix | Description | @@ -79,7 +93,7 @@ These require an `update_key` (shared secret) rather than user authentication. | Method | Path | Auth | Description | |---|---|---|---| -| GET | `/` | No | Health check | +| GET | `/` | No | Health check, available login methods | | PUT | `/auth/register` | No | Register user | | POST | `/auth/login` | No | Log in | | GET | `/auth/refresh` | Yes | Refresh token | diff --git a/src/content/docs/architecture/auth-model.md b/src/content/docs/architecture/auth-model.md index 7b0e4f9..e0aebde 100644 --- a/src/content/docs/architecture/auth-model.md +++ b/src/content/docs/architecture/auth-model.md @@ -129,6 +129,19 @@ The backend also: [Local SLURM setup](/for_developers/local-setup/local-slurm/) emulates the PLGrid auth infrastructure locally. It creates a Keycloak instance and a mock certificate authority server. Keycloak config can be viewed [here](https://github.com/yaptide/yaptide/blob/68bbbf86a37b120a2708fe515e8256f1e23f28a7/slurm/keycloak/yaptide-realm.json). The mock certificate authority uses the private key `/slurm/ca_key/ca_key` to sign the certs. Entrypoint script puts the public key `/slurm/ca_key/ca_key.pub` into the Slurm cluster and configures it to trust any certificates signed by that authority. +## Keycloak-Only Deployments + +Instances that authenticate exclusively through Keycloak (e.g. `yaptide.c3.plgrid.pl`) can switch native auth off with backend environment variables. Values `false`, `0`, `no`, `off` and empty disable a flag; when unset, both flags are enabled. + +| Variable | Default | Effect when set to `false` | +|---|---|---| +| `ENABLE_USER_REGISTRATION` | `true` (`false` in `docker-compose.yml`) | `PUT /auth/register` returns `403`. Existing local users can still log in. | +| `ENABLE_LOCAL_USERS` | `true` | Local users are disabled completely: register and login return `403`, and `@requires_auth` rejects tokens of local users (including refresh tokens issued before the switch). Implies registration is off. | + +Keycloak login (`POST /auth/keycloak`) is not affected by either flag. + +The backend advertises the current settings on the root endpoint (`GET /` returns `local_users_enabled` and `registration_enabled`). The UI reads them during its reachability check and hides the "use password login" option when local users are disabled, so no separate frontend setting is needed. + ## Demo Mode When `REACT_APP_TARGET=demo`, authentication is bypassed entirely and only in-browser Geant4 simulations are available. See [Frontend Demo — Local](/for_developers/local-setup/local-frontend-demo/) for setup instructions. @@ -142,7 +155,8 @@ All protected endpoints use the `@requires_auth()` decorator, which: 1. Extracts the JWT access token from the `access_token` cookie 2. Decodes and validates the token (signature, expiry) 3. Loads the `UserModel` from the database -4. Injects the `user` object into the Flask request context +4. Rejects local (`YaptideUserModel`) users with `403` when `ENABLE_LOCAL_USERS` is off +5. Injects the `user` object into the Flask request context ```python @requires_auth() diff --git a/src/content/docs/backend/docker-deployment.md b/src/content/docs/backend/docker-deployment.md index 8ac5634..6270254 100644 --- a/src/content/docs/backend/docker-deployment.md +++ b/src/content/docs/backend/docker-deployment.md @@ -123,6 +123,10 @@ Set these in a `.env` file in the `yaptide/` root or pass them via Docker: | `KEYCLOAK_BASE_URL` | Keycloak server URL | | `KEYCLOAK_REALM` | Keycloak realm | | `CERT_AUTH_URL` | PLGrid cert-auth service URL | +| `ENABLE_USER_REGISTRATION` | Allow self-registration of local users. `docker-compose.yml` defaults it to `false` | +| `ENABLE_LOCAL_USERS` | Allow local (username/password) users at all. Defaults to `true`; set to `false` for Keycloak-only instances | + +See [Keycloak-only deployments](/for_developers/architecture/auth-model/#keycloak-only-deployments) for details. ### Simulator Storage (S3) diff --git a/src/content/docs/backend/overview.md b/src/content/docs/backend/overview.md index 11a6426..9890632 100644 --- a/src/content/docs/backend/overview.md +++ b/src/content/docs/backend/overview.md @@ -102,6 +102,8 @@ All large data (input files, simulation results, logs) is **gzip-compressed** be | `KEYCLOAK_BASE_URL` | Keycloak server URL | | `KEYCLOAK_REALM` | Keycloak realm | | `CERT_AUTH_URL` | PLGrid SSH cert service URL | +| `ENABLE_USER_REGISTRATION` | Allow `PUT /auth/register` (default `true`) | +| `ENABLE_LOCAL_USERS` | Allow native username/password users at all (default `true`); `false` makes the instance Keycloak-only | | `MAX_CORES` | CPU limit for simulation worker | | `LOG_LEVEL_ROOT` | Logging verbosity | diff --git a/src/content/docs/frontend/auth-flows.md b/src/content/docs/frontend/auth-flows.md index 342f006..6086b45 100644 --- a/src/content/docs/frontend/auth-flows.md +++ b/src/content/docs/frontend/auth-flows.md @@ -13,6 +13,8 @@ The frontend supports two authentication modes and a demo mode that bypasses aut | **Keycloak SSO** (PLGrid) | `REACT_APP_ALT_AUTH=plg` | Yes + Keycloak | | **Demo** (no auth) | `REACT_APP_TARGET=demo` | No | +In Keycloak SSO mode the login panel also offers a "use password login" link. It is hidden when the backend reports `local_users_enabled: false` on `GET /` (read by the reachability check in `AuthService.tsx` and exposed as `localUsersEnabled` on the auth context). Backends that do not report the flag are treated as allowing local users. + ## Standard Authentication ### Login Flow From 0914b25a48784795523bc790f765d57f5a9290af Mon Sep 17 00:00:00 2001 From: Leszek Grzanka Date: Thu, 1 Oct 2026 20:34:22 +0200 Subject: [PATCH 2/3] Document single ENABLE_LOCAL_USERS flag, disabled by default The backend merged ENABLE_USER_REGISTRATION into ENABLE_LOCAL_USERS, and local users are now off unless the flag is true. Update the auth reference and env-var tables, and add the flag to the local and Docker setup guides so the admin/password login keeps working. Co-Authored-By: Claude Opus 5.5 --- src/content/docs/api-reference/auth.md | 10 +++++----- src/content/docs/api-reference/overview.md | 5 ++--- src/content/docs/architecture/auth-model.md | 18 +++++++++--------- src/content/docs/backend/docker-deployment.md | 3 +-- src/content/docs/backend/overview.md | 3 +-- src/content/docs/backend/testing.md | 2 +- .../docs/docker-setup/docker-celery.mdx | 6 ++++++ src/content/docs/local-setup/local-celery.mdx | 6 ++++-- 8 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/content/docs/api-reference/auth.md b/src/content/docs/api-reference/auth.md index f10874e..b0e4e44 100644 --- a/src/content/docs/api-reference/auth.md +++ b/src/content/docs/api-reference/auth.md @@ -4,7 +4,7 @@ description: Authentication and session management API. --- :::note -Native username/password auth can be disabled per instance with the `ENABLE_USER_REGISTRATION` and `ENABLE_LOCAL_USERS` environment variables. See [Keycloak-only deployments](/for_developers/architecture/auth-model/#keycloak-only-deployments). +Native username/password auth is available only when the backend runs with `ENABLE_LOCAL_USERS=true`. Otherwise register, login and refresh return `403`. See [Keycloak-only deployments](/for_developers/architecture/auth-model/#keycloak-only-deployments). ::: ## Register @@ -32,7 +32,7 @@ Content-Type: application/json **Errors:** - `400` — Missing username or password -- `403` — User already exists, or registration is disabled on this instance (`ENABLE_USER_REGISTRATION` or `ENABLE_LOCAL_USERS` set to `false`) +- `403` — User already exists, or local users are disabled on this instance (`ENABLE_LOCAL_USERS` not `true`) --- @@ -67,7 +67,7 @@ Content-Type: application/json **Errors:** - `401` — Invalid credentials - `400` — Missing fields -- `403` — Local users are disabled on this instance (`ENABLE_LOCAL_USERS=false`) +- `403` — Local users are disabled on this instance (`ENABLE_LOCAL_USERS` not `true`) --- @@ -94,7 +94,7 @@ Cookie: refresh_token= **Errors:** - `401` — Invalid or expired refresh token -- `403` — Local users are disabled on this instance (`ENABLE_LOCAL_USERS=false`) +- `403` — Local users are disabled on this instance (`ENABLE_LOCAL_USERS` not `true`) --- @@ -119,7 +119,7 @@ Cookie: access_token= The `source` field indicates the authentication provider: `"local"` or `"keycloak"`. -When `ENABLE_LOCAL_USERS=false`, this and every other protected endpoint return `403` for local users, even with a token issued before the switch. +When local users are disabled, this and every other protected endpoint return `403` for local users, even with a token issued before the switch. --- diff --git a/src/content/docs/api-reference/overview.md b/src/content/docs/api-reference/overview.md index d9f84f0..1c0f759 100644 --- a/src/content/docs/api-reference/overview.md +++ b/src/content/docs/api-reference/overview.md @@ -26,7 +26,7 @@ Two authentication modes: Protected endpoints return `401 Unauthorized` if no valid token is present. -Native auth can be disabled per instance (see [Keycloak-only deployments](/for_developers/architecture/auth-model/#keycloak-only-deployments)). The root endpoint reports which methods are available: +Native auth is disabled unless the backend sets `ENABLE_LOCAL_USERS=true` (see [Keycloak-only deployments](/for_developers/architecture/auth-model/#keycloak-only-deployments)). The root endpoint reports whether it is enabled: ```http GET / @@ -35,8 +35,7 @@ GET / ```json { "message": "Hello World!", - "local_users_enabled": true, - "registration_enabled": true + "local_users_enabled": true } ``` diff --git a/src/content/docs/architecture/auth-model.md b/src/content/docs/architecture/auth-model.md index e0aebde..418838c 100644 --- a/src/content/docs/architecture/auth-model.md +++ b/src/content/docs/architecture/auth-model.md @@ -9,7 +9,7 @@ YAPTIDE supports two authentication methods: **native Yaptide auth** (username/p | Method | When Used | Users | |---|---|---| -| **Yaptide Native** | Development, standalone deployments | Any registered user | +| **Yaptide Native** | Development, standalone deployments (requires `ENABLE_LOCAL_USERS=true`) | Any registered user | | **Keycloak SSO** | Production, PLGrid-integrated deployments | PLGrid-federated users | ## Native Authentication Flow @@ -131,16 +131,16 @@ The backend also: ## Keycloak-Only Deployments -Instances that authenticate exclusively through Keycloak (e.g. `yaptide.c3.plgrid.pl`) can switch native auth off with backend environment variables. Values `false`, `0`, `no`, `off` and empty disable a flag; when unset, both flags are enabled. +Native auth is controlled by a single backend environment variable, `ENABLE_LOCAL_USERS`. Local users are **disabled by default**: unless the variable is set to a truthy value (`true`, `1`, `yes`, `on`), the instance accepts Keycloak users only. The value is parsed with [environs](https://github.com/sloria/environs); an invalid value is logged and treated as disabled. -| Variable | Default | Effect when set to `false` | -|---|---|---| -| `ENABLE_USER_REGISTRATION` | `true` (`false` in `docker-compose.yml`) | `PUT /auth/register` returns `403`. Existing local users can still log in. | -| `ENABLE_LOCAL_USERS` | `true` | Local users are disabled completely: register and login return `403`, and `@requires_auth` rejects tokens of local users (including refresh tokens issued before the switch). Implies registration is off. | +| `ENABLE_LOCAL_USERS` | Behaviour | +|---|---| +| `true` | `PUT /auth/register` and `POST /auth/login` work, and local users can use protected endpoints. | +| unset, `false` or invalid | Register and login return `403`, and `@requires_auth` rejects tokens of local users, including refresh tokens issued before the switch. | -Keycloak login (`POST /auth/keycloak`) is not affected by either flag. +Keycloak login (`POST /auth/keycloak`) is not affected. Users created with `db_manage.py add-user` are local users too, so they can log in only when the flag is `true`. -The backend advertises the current settings on the root endpoint (`GET /` returns `local_users_enabled` and `registration_enabled`). The UI reads them during its reachability check and hides the "use password login" option when local users are disabled, so no separate frontend setting is needed. +The backend advertises the setting on the root endpoint (`GET /` returns `local_users_enabled`). The UI reads it during its reachability check and hides the "use password login" option when local users are disabled, so no separate frontend setting is needed. ## Demo Mode @@ -155,7 +155,7 @@ All protected endpoints use the `@requires_auth()` decorator, which: 1. Extracts the JWT access token from the `access_token` cookie 2. Decodes and validates the token (signature, expiry) 3. Loads the `UserModel` from the database -4. Rejects local (`YaptideUserModel`) users with `403` when `ENABLE_LOCAL_USERS` is off +4. Rejects local (`YaptideUserModel`) users with `403` unless `ENABLE_LOCAL_USERS=true` 5. Injects the `user` object into the Flask request context ```python diff --git a/src/content/docs/backend/docker-deployment.md b/src/content/docs/backend/docker-deployment.md index 6270254..66805ec 100644 --- a/src/content/docs/backend/docker-deployment.md +++ b/src/content/docs/backend/docker-deployment.md @@ -123,8 +123,7 @@ Set these in a `.env` file in the `yaptide/` root or pass them via Docker: | `KEYCLOAK_BASE_URL` | Keycloak server URL | | `KEYCLOAK_REALM` | Keycloak realm | | `CERT_AUTH_URL` | PLGrid cert-auth service URL | -| `ENABLE_USER_REGISTRATION` | Allow self-registration of local users. `docker-compose.yml` defaults it to `false` | -| `ENABLE_LOCAL_USERS` | Allow local (username/password) users at all. Defaults to `true`; set to `false` for Keycloak-only instances | +| `ENABLE_LOCAL_USERS` | Allow local (username/password) users. Defaults to `false` (Keycloak-only); set to `true` to log in with users created by `db_manage.py add-user` | See [Keycloak-only deployments](/for_developers/architecture/auth-model/#keycloak-only-deployments) for details. diff --git a/src/content/docs/backend/overview.md b/src/content/docs/backend/overview.md index 9890632..5ab30b4 100644 --- a/src/content/docs/backend/overview.md +++ b/src/content/docs/backend/overview.md @@ -102,8 +102,7 @@ All large data (input files, simulation results, logs) is **gzip-compressed** be | `KEYCLOAK_BASE_URL` | Keycloak server URL | | `KEYCLOAK_REALM` | Keycloak realm | | `CERT_AUTH_URL` | PLGrid SSH cert service URL | -| `ENABLE_USER_REGISTRATION` | Allow `PUT /auth/register` (default `true`) | -| `ENABLE_LOCAL_USERS` | Allow native username/password users at all (default `true`); `false` makes the instance Keycloak-only | +| `ENABLE_LOCAL_USERS` | Allow native username/password users (register, login). Default: disabled, so the instance is Keycloak-only | | `MAX_CORES` | CPU limit for simulation worker | | `LOG_LEVEL_ROOT` | Logging verbosity | diff --git a/src/content/docs/backend/testing.md b/src/content/docs/backend/testing.md index a007a02..4ded9a9 100644 --- a/src/content/docs/backend/testing.md +++ b/src/content/docs/backend/testing.md @@ -126,7 +126,7 @@ def test_health_check(client): ### Authenticated Tests -Most endpoints require authentication. Use the login fixture: +Most endpoints require authentication. `pytest.ini` sets `ENABLE_LOCAL_USERS=true` so tests can register and log in local users: ```python def test_submit_simulation(client): diff --git a/src/content/docs/docker-setup/docker-celery.mdx b/src/content/docs/docker-setup/docker-celery.mdx index ce83f9f..db2fab9 100644 --- a/src/content/docs/docker-setup/docker-celery.mdx +++ b/src/content/docs/docker-setup/docker-celery.mdx @@ -38,6 +38,12 @@ Navigate to the `yaptide/` directory: cd yaptide ``` +Local (username/password) users are disabled by default. To log in with the user created below, create a `.env` file in the `yaptide/` directory first: + +```bash title="yaptide/.env" +ENABLE_LOCAL_USERS=true +``` + Two startup options are available: **Standard mode** — builds and starts all containers: diff --git a/src/content/docs/local-setup/local-celery.mdx b/src/content/docs/local-setup/local-celery.mdx index 9f7fb22..88c5028 100644 --- a/src/content/docs/local-setup/local-celery.mdx +++ b/src/content/docs/local-setup/local-celery.mdx @@ -176,14 +176,14 @@ Open a third backend terminal and go to the `yaptide/` directory. Run: ```bash -FLASK_SQLALCHEMY_ECHO=True FLASK_USE_CORS=True FLASK_SQLALCHEMY_DATABASE_URI="sqlite:///db.sqlite" CELERY_BROKER_URL=redis://127.0.0.1:6379/0 CELERY_RESULT_BACKEND=redis://127.0.0.1:6379/0 poetry run flask --debug --app yaptide.application run +FLASK_SQLALCHEMY_ECHO=True FLASK_USE_CORS=True ENABLE_LOCAL_USERS=true FLASK_SQLALCHEMY_DATABASE_URI="sqlite:///db.sqlite" CELERY_BROKER_URL=redis://127.0.0.1:6379/0 CELERY_RESULT_BACKEND=redis://127.0.0.1:6379/0 poetry run flask --debug --app yaptide.application run ``` ```powershell -$env:FLASK_SQLALCHEMY_ECHO="True"; $env:FLASK_USE_CORS="True"; $env:FLASK_SQLALCHEMY_DATABASE_URI="sqlite:///db.sqlite"; $env:CELERY_BROKER_URL="redis://127.0.0.1:6379/0"; $env:CELERY_RESULT_BACKEND="redis://127.0.0.1:6379/0"; poetry run flask --debug --app yaptide.application run +$env:FLASK_SQLALCHEMY_ECHO="True"; $env:FLASK_USE_CORS="True"; $env:ENABLE_LOCAL_USERS="true"; $env:FLASK_SQLALCHEMY_DATABASE_URI="sqlite:///db.sqlite"; $env:CELERY_BROKER_URL="redis://127.0.0.1:6379/0"; $env:CELERY_RESULT_BACKEND="redis://127.0.0.1:6379/0"; poetry run flask --debug --app yaptide.application run ``` @@ -197,6 +197,8 @@ This creates `db.sqlite` inside `./instance/` (default [Flask instance folder](h `FLASK_SQLALCHEMY_ECHO=True` enables SQL query logging for debugging database interactions. +`ENABLE_LOCAL_USERS=true` allows logging in with a username and password. Local users are disabled by default, so without it the login in the next steps fails with `403`. + ### 7. Create a user Before logging in from the frontend, you need to create a user in the database. Open the 4th terminal and go to the `yaptide/` directory. Run: From da2c361b74558dfd2656d2f4c3620fe8c9b01bb6 Mon Sep 17 00:00:00 2001 From: Leszek Grzanka Date: Thu, 1 Oct 2026 20:47:38 +0200 Subject: [PATCH 3/3] Tell developers to enable local users for local testing Local users are now off by default, so the Docker quick start must set ENABLE_LOCAL_USERS=true before logging in with a db_manage user. Add a troubleshooting tip with the exact 403 message to the local and Docker Celery setup guides, and note that changing .env requires recreating the Flask container. Co-Authored-By: Claude Opus 5.5 --- src/content/docs/backend/docker-deployment.md | 5 +++++ src/content/docs/docker-setup/docker-celery.mdx | 4 ++++ src/content/docs/local-setup/local-celery.mdx | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/content/docs/backend/docker-deployment.md b/src/content/docs/backend/docker-deployment.md index 66805ec..2d32488 100644 --- a/src/content/docs/backend/docker-deployment.md +++ b/src/content/docs/backend/docker-deployment.md @@ -38,8 +38,11 @@ services: ## Quick Start +Local (username/password) users are disabled by default, which makes the instance Keycloak-only. For local development and testing, enable them before starting the stack, otherwise the user created below cannot log in: + ```bash cd yaptide +echo "ENABLE_LOCAL_USERS=true" >> .env docker compose up --build -d ``` @@ -56,6 +59,8 @@ docker compose exec yaptide_flask python -m yaptide.admin.db_manage add-user \ --username admin --password admin123 ``` +If the stack is already running, changing `.env` takes effect only after the Flask container is recreated (`docker compose up -d yaptide_flask`); `docker compose restart` keeps the old environment. + ## Compose Variants ### Standard (Production-like) diff --git a/src/content/docs/docker-setup/docker-celery.mdx b/src/content/docs/docker-setup/docker-celery.mdx index db2fab9..1c7d954 100644 --- a/src/content/docs/docker-setup/docker-celery.mdx +++ b/src/content/docs/docker-setup/docker-celery.mdx @@ -126,6 +126,10 @@ docker compose up Open **http://localhost** and log in using credentials you created (username: `admin`, password: `password`). The setup is complete now. +:::tip[Login fails with 403?] +If logging in shows *Local user login is disabled on this instance*, the backend was started without `ENABLE_LOCAL_USERS=true`. Local users are disabled by default, so add it to `yaptide/.env` as described above and re-run the start script, which recreates the containers with the new setting. +::: + :::caution With a Chromium-based browser, use `https://localhost:8443` as the backend URL instead of `http://localhost:5000` to avoid cookie issues with browser security policies. ```bash title="ui/.env" diff --git a/src/content/docs/local-setup/local-celery.mdx b/src/content/docs/local-setup/local-celery.mdx index 88c5028..49987ab 100644 --- a/src/content/docs/local-setup/local-celery.mdx +++ b/src/content/docs/local-setup/local-celery.mdx @@ -268,6 +268,10 @@ npm run start Open **http://localhost:3000**. Log in with the credentials you created (username: `admin`, password: `password`). The setup is complete now. The page reloads on edits. +:::tip[Login fails with 403?] +If logging in shows *Local user login is disabled on this instance*, the backend was started without `ENABLE_LOCAL_USERS=true`. Local users are disabled by default, so add `ENABLE_LOCAL_USERS=true` to the Flask command in [step 6](#6-start-the-flask-api) and restart it. +::: + :::caution Access both frontend and backend using the **same domain** — either both `localhost` or both `127.0.0.1`. Mixing them breaks cookie-based authentication (`SameSite=Lax` policy). :::