Skip to main content

Configuration

Operators configure identity-service with environment variables (see .env.example in the repository root). Copy it to .env for local runs; pass the same keys to Docker, Coolify, or your orchestrator in production.

Published docs: https://docs.shellui.com/identity (GitHub Pages cname on release tags).


Required in production (DEBUG=false)​

VariablePurpose
SECRET_KEYDjango session and CSRF signing (required always)
JWT_PRIVATE_KEYRS256 JWT signing PEM (generate_jwt_keys management command)
JWT_ISSUERiss claim on issued JWTs; verifiers should validate
JWT_AUDIENCEaud claim on issued JWTs
ALLOWED_HOSTSComma-separated hostnames (no scheme)

Typical browser/OAuth hardening:

VariablePurpose
CSRF_TRUSTED_ORIGINSFull HTTPS URLs for admin and OAuth confirmation pages behind TLS

Details: JWKS and JWT verification, Security hardening.


JWT and token lifetimes​

VariableDefault / notes
JWT_PUBLIC_KEY, JWT_KEY_IDOptional; derived from private key when omitted
JWT_PREVIOUS_PUBLIC_KEY, JWT_PREVIOUS_KEY_IDOptional rotation — previous key stays in JWKS
JWT_ACCEPT_HS256_LEGACYfalse in production with RS256; set true only while migrating old HS256 tokens
JWT_ACCESS_TOKEN_LIFETIME5m (supports s, m, h, d suffixes)
JWT_REFRESH_TOKEN_LIFETIME7d
PERSONAL_ACCESS_TOKEN_LIFETIME30d (default since v0.5.0)

OAuth token delivery​

VariableDefaultPurpose
OAUTH_TOKEN_DELIVERYcodecode = one-time shellui_auth_code + POST /api/v1/oauth/session; fragment = legacy URL hash tokens
OAUTH_SESSION_CODE_TTL_SECONDS120Lifetime of the one-time auth code
OAUTH_ALLOW_LOOPBACK_REDIRECTSfollows DEBUGAllow redirect_to to loopback for CLI/local shells when true
OAUTH_SKIP_CONFIRM_PROVIDERSgoogle when unsetProvider slugs that skip the identity-hosted account confirmation page after callback (comma-separated; empty env disables skips)

OAuth account linking (Shellui /api/v1/authorize callback):

  • Match an existing user by provider user id (SocialAccount) first.
  • Match by email only when the provider proves ownership:
    • Google: email_verified is true in the userinfo response.
    • GitHub: verified primary email from /user/emails.
    • Microsoft: xms_edov is true in the ID token, or the OAuth client uses a dedicated tenant (not common). Shellui does not trust mail or userPrincipalName from the common tenant alone.
  • If verification fails, Shellui returns an error and does not attach the sign-in to another user's email.

OAuth login CSRF: /api/v1/authorize stores a random nonce in the HttpOnly shellui_oauth_state_nonce cookie (SameSite=Lax). The callback checks that nonce against signed state and rejects reused state values.

Flow and allowlist: OAuth login.


VariableDefaultPurpose
MAGIC_LINK_ENABLEDtrueGlobal kill switch for magic-link request/verify endpoints
MAGIC_LINK_TTL_SECONDS1800One-time link lifetime (seconds)
AUTH_RATE_LIMIT_MAGIC_LINK10Requests per minute bucket (per client IP and per email+company)

Links are built from JWT_ISSUER (HTTPS when DEBUG=false). See magic-link.md.


CORS (browser API calls)​

VariableDefaultPurpose
CORS_ALLOW_ALL_ORIGINStrueBearer JWT APIs from arbitrary shell origins (credentials off)
CORS_ALLOW_CREDENTIALSfalseMust stay false with allow-all
CORS_ALLOWED_ORIGINSemptyLock-down: comma-separated origins when allow-all is false
CORS_ALLOWED_ORIGIN_REGEXESemptyOptional regex allow list

OAuth redirect allowlist (CompanyOAuthRedirect) is separate and stricter — see oauth-login.md.


Database and health​

VariableDefaultPurpose
POSTGRES_DATABASE_URLemptyPostgres DSN; omit for SQLite (SQLITE_PATH or /app/data/db.sqlite3 in Docker)
POSTGRES_SSL_REQUIREtrue when not DEBUGTLS to Postgres
POSTGRES_CONNECT_TIMEOUT10Seconds; avoids workers stuck on dead TCP
GET /health/live—Liveness probe — no session/DB; use for load balancers (not / alone)

SQLite uses WAL mode on connect for better single-node concurrency (v0.5.1+).


Gunicorn (Docker entrypoint)​

VariableDefaultPurpose
GUNICORN_WORKERS4Sync worker processes
GUNICORN_THREADS4Threads per worker (gthread worker class)
GUNICORN_TIMEOUT60Worker request timeout (OAuth may use long outbound HTTP)

Concurrency ≈ workers × threads. Under-provisioned pools can queue even simple requests when OAuth holds workers.


Security, proxies, and rate limits​

VariablePurpose
TRUSTED_PROXY_IPSComma-separated IPs/CIDRs; when REMOTE_ADDR matches, client IP is the rightmost untrusted hop in X-Forwarded-For (see security-hardening.md)
SELF_SERVICE_ACCOUNT_DELETE_MAX_IAT_AGEMax age of access JWT auth_time (interactive sign-in time) for DELETE /api/v1/user (default 5m; not PATs; refresh preserves auth_time)
AUTH_RATE_LIMIT_ENABLEDtrue — disable only for debugging
AUTH_RATE_LIMIT_OAUTH, _TOKEN_REFRESH, _SETTINGS, _ADMIN_LOGIN, _PATPer-endpoint limits (see security-hardening.md)
SECURE_SSL_REDIRECT, SECURE_HSTS_SECONDS, SESSION_COOKIE_SECURE, CSRF_COOKIE_SECUREHTTPS defaults when DEBUG=false

Shared cache (Redis, develop / v0.6.0+)​

VariableDefaultPurpose
REDIS_URLunsetDjango Redis cache backend for auth rate limits, logout access-token denylist, and last-seen throttling

When unset, Django uses in-process LocMem (fine for local dev or a single Gunicorn worker). With GUNICORN_WORKERS > 1, set REDIS_URL so limits and denylists are shared across workers. manage.py check --deploy emits authapi.W002 when production uses LocMem with multiple workers.

Examples:

# Docker Compose / Coolify internal Redis
REDIS_URL=redis://redis:6379/0

See PUBLISH.md for Coolify Redis setup.


Enterprise SCIM (develop / v0.6.0+)​

VariableDefaultPurpose
SCIM_ENABLEDtrueWhen false, SCIM routes return 404 (emergency kill switch)

Documented in SCIM. Available on develop after migrations.


Bootstrap and observability​

VariablePurpose
DEBUGDevelopment mode; never true in production
SETUP_TOKENOne-time web superuser bootstrap when DEBUG=false (prefer createsuperuser)
IDENTITY_SERVICE_PORTLocal/docker-compose port hint
SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_RELEASE, SENTRY_TRACES_SAMPLE_RATEOptional error reporting
EMAIL_*, DEFAULT_FROM_EMAILSMTP for company access notifications (company-access.md)
ACTIONS_WEBHOOK_TIMEOUT_SECONDSWebhook POST timeout (default 5) — see actions.md
ACTIONS_OUTBOX_MAX_ATTEMPTSOutbox delivery retries (default 8)
ACTIONS_WEBHOOK_ALLOW_PRIVATEAllow webhooks to private IPs (default false)
ACTIONS_WEBHOOK_RETRY_LEASE_SECONDSRetry worker lease (default 120)
ACTIONS_WEBHOOK_DISPATCH_WORKERSPost-commit delivery thread pool size (default 4)
MAGIC_LINK_EMAIL_DEFAULT_LANGUAGEMagic-link email locale fallback (default en)