Skip to content

Security Configuration

Authentication and CAPTCHA settings.

JWT Authentication

VariableDefaultSensitiveDescription
JWT_SECRET_KEY(auto-generated)YesJWT signing key. Auto-generated if not set. Set a stable value in production.
JWT_ALGORITHMHS256NoJWT signing algorithm.
ACCESS_TOKEN_EXPIRE_HOURS24NoAccess token expiration in hours.
REFRESH_TOKEN_EXPIRE_DAYS7NoRefresh token expiration in days.

WARNING

If JWT_SECRET_KEY is not set, a random key is generated at startup. This means all active sessions will be invalidated on every restart. Always set a stable key in production.

Web Push (VAPID)

VAPID keys are used to authenticate browser Web Push notifications.

VariableDefaultSensitiveDescription
VAPID_PUBLIC_KEY(auto-generated)NoPublic VAPID key sent to browsers for push subscription.
VAPID_PRIVATE_KEY(auto-generated)YesPrivate VAPID key used by the server to sign push requests.
VAPID_SUBJECTmailto:admin@example.comNoContact subject included in VAPID claims. Use a mailto: address or an HTTPS URL you control.

WARNING

For production and multi-instance deployments, set a stable VAPID key pair. If the key pair changes, existing browser push subscriptions may stop receiving notifications and users may need to subscribe again.

Generate a key pair from the project root:

bash
uv run python - <<'PY'
import base64
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import (
    Encoding,
    NoEncryption,
    PrivateFormat,
    PublicFormat,
)

private_key = ec.generate_private_key(ec.SECP256R1())
private_der = private_key.private_bytes(
    Encoding.DER,
    PrivateFormat.PKCS8,
    NoEncryption(),
)
public_raw = private_key.public_key().public_bytes(
    Encoding.X962,
    PublicFormat.UncompressedPoint,
)

print("VAPID_PUBLIC_KEY=" + base64.urlsafe_b64encode(public_raw).decode())
print("VAPID_PRIVATE_KEY=" + base64.urlsafe_b64encode(private_der).decode())
print("VAPID_SUBJECT=mailto:admin@example.com")
PY

Then paste the output into .env or your production secret manager. Change VAPID_SUBJECT to your real administrator email, for example mailto:ops@example.com.

Cloudflare Turnstile (CAPTCHA)

VariableDefaultSensitiveDescription
TURNSTILE_ENABLEDfalseNoEnable Cloudflare Turnstile CAPTCHA.
TURNSTILE_SITE_KEY(empty)NoTurnstile site key (used in frontend).
TURNSTILE_SECRET_KEY(empty)YesTurnstile secret key (used in backend).
TURNSTILE_REQUIRE_ON_LOGINfalseNoRequire CAPTCHA on login.
TURNSTILE_REQUIRE_ON_REGISTERtrueNoRequire CAPTCHA on registration.
TURNSTILE_REQUIRE_ON_PASSWORD_CHANGEtrueNoRequire CAPTCHA on password change.

User Management

VariableDefaultDescription
DEFAULT_USER_ROLEuserDefault role for new users.
ENABLE_REGISTRATIONtrueEnable user registration.
ADMIN_CONTACT_EMAIL(empty)Admin contact email displayed in the UI.
ADMIN_CONTACT_URL(empty)Admin contact URL displayed in the UI.

Example

bash
# JWT
JWT_SECRET_KEY=your-stable-secret-key-at-least-32-chars
JWT_ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_HOURS=24
REFRESH_TOKEN_EXPIRE_DAYS=7

# Web Push (VAPID)
VAPID_PUBLIC_KEY=your-generated-vapid-public-key
VAPID_PRIVATE_KEY=your-generated-vapid-private-key
VAPID_SUBJECT=mailto:ops@example.com

# Turnstile CAPTCHA
TURNSTILE_ENABLED=true
TURNSTILE_SITE_KEY=0x4AAAAAAA
TURNSTILE_SECRET_KEY=0x4AAAAAAA
TURNSTILE_REQUIRE_ON_REGISTER=true

# User Management
ENABLE_REGISTRATION=true
DEFAULT_USER_ROLE=user