Skip to content

Environment variables

Environment variables

The complete reference. Values come from .env; the authoritative list is .env.example plus config/cbox-id.php (this app) and the framework config it merges from (cboxdk/laravel-id). Run php artisan cbox-id:doctor any time to have the security-critical ones checked for you.

Identity platform (required)

Variable What it does Default When to change
CBOX_ID_CRYPTO_KEY base64 of 32 random bytes — the current master key for envelope encryption of sealed secrets (signing keys, connection creds, push tokens). Everything new is sealed under it. (none — generated by cbox-id:install) Set once at install. Back it up separately from the DB; losing it makes sealed secrets unrecoverable. To change it, follow Rotating the crypto master key — swapping it without keeping the old one in CBOX_ID_CRYPTO_PREVIOUS_KEYS makes every existing secret unreadable.
CBOX_ID_CRYPTO_PREVIOUS_KEYS Comma-separated previous master keys, same form as the key above, kept only to open secrets sealed before a rotation. Nothing is ever sealed under them. A malformed one refuses to boot, naming its position. (empty) During a master-key rotation: put the old key here, deploy, run cbox-id:crypto:rewrap, and remove it once cbox-id:doctor says nothing needs it.
CBOX_ID_ERASURE_PSEUDONYM_KEY The secret the placeholders of an erased person are derived from (erased+<hmac>@erased.invalid). (derived from the crypto master key) Set it before the first erasure if you want the placeholders to survive a master-key rotation unchanged; otherwise leave it unset.
CBOX_ID_ISSUER The public HTTPS URL of this IdP — the token iss and OIDC discovery base. falls back to APP_URL Set to the exact public URL clients discover, e.g. https://id.acme.com.
CBOX_ID_WEBAUTHN_RP_ID Passkey Relying-Party ID — usually the registrable domain. localhost (in .env.example) Set to your bare domain in production, e.g. id.acme.com.
CBOX_ID_WEBAUTHN_ORIGIN Passkey origin — the exact scheme+host+port the browser reports. ${APP_URL} Set to the full origin, e.g. https://id.acme.com. A mismatch is rejected.

Environments (multi-plane hosting)

An environment is the hard identity boundary — its own users, signing keys, and issuer — resolved per request from the host.

Variable What it does Default When to change
CBOX_ID_ENVIRONMENT_DEFAULT The fallback environment (plane) key used when the request host maps to none. (empty) Set it for a single-tenant / on-prem install (all traffic lands on one plane). Leave empty for multi-tenant hosting, where an unknown host is refused rather than defaulted.
CBOX_ID_ENVIRONMENT_BASE_DOMAINS Comma list of base domains under which a leading subdomain label resolves to an environment (e.g. staging.auth.example.com → the staging plane). (empty) Set when you host multiple planes as subdomains. A host is trusted for slug resolution only if it sits under one of these, so a spoofed Host can never select a plane. Leave empty to require exact custom-domain matches. Deployment-critical for multi-environment hosting. Setting it also switches on the host-plane bulkheads — see the IdP-surface gate.
CBOX_ID_ENVIRONMENT_RESOLUTION_CACHE_TTL Seconds a host → environment resolution stays cached. Every request resolves its environment before any endpoint logic runs; uncached that is 2–3 queries against a table that changes approximately never. 60 Rarely. Creating, renaming, re-domaining or suspending an environment — and suspending or reactivating its workspace — drop the cache entry immediately, so the off-switch still bites on the next request. The TTL only bounds the one case that cannot be derived from the row: a slug rename, where the old subdomain keeps resolving until the entry lapses. Set 0 to disable caching entirely (correctness is unchanged; you pay the queries on every request).

Self-service signup

Variable What it does Default When to change
CBOX_ID_SIGNUP_MODE Who may self-register at /signup: open (anyone), invite_only (public signup closed, admin invitations still work), or closed (no self-service at all). Admin/operator provisioning is never gated by this. open Set to invite_only or closed for a private/internal deployment. See Security.

Note that a self-serve signup provisions the workspace, its owner and its first project immediately, but its environment only once the owner confirms their email address — so an unverified signup never stands up a routable IdP. Nothing configures this; it is how self-serve signup works. See Adaptive risk.

Bot protection (CAPTCHA)

Optional. Wires Cloudflare Turnstile in as the CAPTCHA for a signup the risk scorer challenges (it is never shown to everyone). Both keys must be set for the feature to exist at all: with either missing, no widget renders, no Cloudflare script is loaded, the CSP keeps its strict same-origin script-src, and signup behaves exactly as it does without the feature. The challenge only bites when RISK_MODE=enforce.

Variable What it does Default When to change
CBOX_ID_TURNSTILE_SITE_KEY The Turnstile site key (public) — rendered into the widget on a challenged signup, and what opens the CSP to https://challenges.cloudflare.com. (empty — feature off) Set both keys to switch bot protection on. Get them from the Cloudflare dashboard (Turnstile → add a widget for your signup hostname).
CBOX_ID_TURNSTILE_SECRET_KEY The Turnstile secret key — used server-side to verify the widget's token against Cloudflare's siteverify. Never sent to the browser. (empty — feature off) As above. Treat it like any other secret; a leaked secret lets someone else validate tokens against your widget.

Enterprise self-serve (SSO, SCIM & Admin Portal)

Gate the self-serve SSO/SCIM screens on a billing-fed entitlement, and tune the external IT-admin setup link. See Enterprise self-serve for the full story.

Variable What it does Default When to change
CBOX_ID_ENTITLEMENT_SSO The namespaced entitlement key whose enabled flag unlocks self-serve SAML/OIDC SSO for an org. Deny-by-default: without it, the SSO screen shows an upsell and its actions abort(403). cbox-id-sso Change only to align with the key your billing system pushes. The cbox-id- prefix keeps it from clashing with entitlements your tenant products push through the same projection.
CBOX_ID_ENTITLEMENT_SCIM The namespaced entitlement key whose enabled flag unlocks self-serve SCIM directory sync. Deny-by-default, same as SSO. cbox-id-scim As above.
CBOX_ID_PORTAL_TTL_MINUTES How long a minted Admin Portal setup link stays redeemable, in minutes, when whoever minted it did not choose (they may pick 5 minutes to 7 days). Links are single-use and only their token hash is stored. 30 Lower it for a tighter window; raise it if your customers' IT teams need longer to act.
CBOX_ID_PORTAL_SESSION_MINUTES How long the setup session a redeemed Admin Portal link opens lasts, counted from redemption. 120 Raise it if setting up an identity provider routinely takes longer.
CBOX_ID_CERTIFICATE_ALERT_MAIL Whether the daily SAML certificate scan mails an organization's owners and admins. The connection.certificate_expiring webhook and the trail entry are always written. true Turn off if you relay the webhook to your customers yourself.

Branding

Override the wordmark/hero without editing Blade.

Variable What it does Default When to change
CBOX_ID_BRAND_NAME Replaces "Cbox ID" in the wordmark and page titles. Cbox ID Set to your product name.
CBOX_ID_BRAND_TAGLINE The sign-in hero headline. One identity layer for every app you ship. Set to your own tagline.
CBOX_ID_BRAND_TRUST_LINE Free text under the hero (e.g. a compliance note). (empty — on purpose) Set only if the claim is actually true for your deployment. Never ship an unearned certification badge.

CBOX_ID_BRAND_NAME is also the product name in the hosted emails and on the error pages.

Languages

The languages used by the hosted pages (sign-in, consent, Admin Portal) and the emails they send. The admin console is always English. An environment can override both values in its own settings. See the languages guide.

Variable What it does Default When to change
CBOX_ID_DEFAULT_LOCALE The language used when neither the app (ui_locales), the visitor's own pick nor their browser names one that is switched on. Also the language of emails an administrator sends. en When most of your users share a language other than English.
CBOX_ID_LOCALES Comma-separated list of the languages that are switched on. The order sets the order of the language menu. Supported: en, da, de, sv, nb, fr; other codes are ignored. en,da,de,sv,nb,fr To leave out languages you cannot support people in.

Login lockout

On by default since laravel-id 1.22: a person whose password is guessed wrong too often inside the window is locked out for a while, then unlocked on their own — never until an administrator intervenes, which would hand anyone who knows an address a way to lock its owner out. A threshold set in the console's Authentication policy (lockoutThreshold on the environment or an organization) wins over the default; the window and duration are deployment-wide only.

Variable What it does Default When to change
CBOX_ID_LOCKOUT_THRESHOLD Failed sign-ins on one account, inside the window, that lock it — when no sign-in rule names a threshold. 10 0 switches the default off (lockout then applies only where a sign-in rule sets a threshold — the behaviour before 1.22).
CBOX_ID_LOCKOUT_WINDOW_MINUTES How long failures accumulate before the count starts again. 15 Rarely. A longer window locks out people who simply mistype now and then.
CBOX_ID_LOCKOUT_DURATION_MINUTES How long a locked account stays locked. 15 Raise it for higher assurance; it is the cost of a guessing run, and also of somebody locking a person out on purpose.

Every failure is on the audit trail (user.sign_in_failed), and the lock itself is user.locked_out.

Sessions

Session lifetime knobs applied by the identity engine (in addition to the standard Laravel SESSION_* keys below).

Variable What it does Default When to change
CBOX_ID_SESSION_TTL_MINUTES Absolute session lifetime before re-authentication. 480 (8h) Lower it for higher-assurance deployments.
CBOX_ID_SESSION_IDLE_MINUTES Idle timeout — inactivity before the session is invalidated. 30 Lower it for shared or high-risk environments.
CBOX_ID_SUPPORT_SESSION_MAX_TTL The longest a support session may run, in seconds — and every token minted for it. The console offers nothing longer. It can only lower the one-hour ceiling; nothing goes under 60. 3600 Lower it if your policy wants support access shorter than an hour.

OAuth / OIDC endpoint policy

These are read as documented. Published config is merged key by key, not block by block, so this app's partial oauth and webauthn blocks — it pins authorization_endpoint_path, rp_id and origin, which are properties of this app's routes rather than of a deployment — override only those keys and leave every framework default beside them intact.

This was not always true. Laravel's mergeConfigFrom is a shallow array_merge, so before cboxdk/laravel-id v0.57.0 each partial block replaced the framework's entirely and every variable below was silently inert. If you are on an older framework release, they still are. Confirm what is actually in effect:

php artisan tinker --execute="var_dump(config('cbox-id.oauth'), config('cbox-id.webauthn'));"

A regression test (tests/Feature/PackageConfigDefaultsTest.php) asserts that no package default is left unreachable at any depth, so this cannot silently regress.

Variable What it does Default When to change
CBOX_ID_DCR_MODE Dynamic Client Registration (RFC 7591) at /oauth/register: mcp, disabled, protected (initial access token required) or open. mcp is open registration held to what an MCP client needs: public clients only (no secret), the authorization-code and refresh grants, https or loopback redirect URIs, and the scopes of /mcp plus the protocol scopes. The person is always shown the consent screen for such a client. mcp (this app; the framework's default is disabled) disabled if no MCP client should sign in without an administrator registering it first. open only if something must register itself with other grants — a self-hosted cbox CLI that registers itself with the device grant, say (provisioning it with php artisan cbox-id:cli:client is better).
CBOX_ID_DCR_INITIAL_ACCESS_TOKEN Bearer token required to register a client when DCR is gated. (none) Set when DCR is protected.
CBOX_ID_DCR_MAX_PER_IP_PER_HOUR How many clients one address may register per hour in open and mcp mode, on top of the per-minute throttle. 0 switches the ceiling off. 20 Raise for an office that onboards many agents at once behind one NAT.
CBOX_ID_CIMD_ENABLED Client ID Metadata Documents: an MCP client may give the https URL of a JSON document describing itself as its client_id, and skip registration. The document is fetched through the SSRF guard and cached; nothing is stored. The consent screen leads with the host that published it. true (this app; the framework's default is false) false to accept registered clients only. The fetch limits (CBOX_ID_CIMD_MAX_BYTES, CBOX_ID_CIMD_TIMEOUT, CBOX_ID_CIMD_CONNECT_TIMEOUT, CBOX_ID_CIMD_MIN_TTL, CBOX_ID_CIMD_DEFAULT_TTL, CBOX_ID_CIMD_MAX_TTL) keep the framework's defaults.
CBOX_ID_MCP_DYNAMIC_CLIENTS Whether a client that registered itself (above, or by a metadata document) may be issued a token for the management plane at /mcp. Read from config/api.php. true false to let only clients an administrator registered — the cbox CLI among them — and management keys reach /mcp.
CBOX_ID_ROOT_MCP_OAUTH Multi-tenant only. Whether the platform root signs MCP clients in for its own /mcp (claude mcp add --transport http cbox-id https://<platform-root>/mcp), one connection for a person's whole workspace. On, the root serves the /mcp resource metadata, an RFC 8414 document written for MCP clients, registration in the mcp profile (only while CBOX_ID_DCR_MODE is mcp, without RFC 7592 management), client ID metadata documents, and /oauth/authorize and the token endpoints for those clients. Every token they get is audienced to the root's /mcp and nothing else, openid is refused, and only someone on a workspace's team or an operator can finish signing in. OpenID Connect discovery, UserInfo, SAML and SCIM stay absent on the root either way. Needs CBOX_ID_MCP_DYNAMIC_CLIENTS on too. Read from config/api.php. true false to keep the root to its own first-party clients (the cbox CLI) and workspace keys. Every surface listed here then answers 404 on the root.
CBOX_ID_MCP_STEP_UP_ACR RFC 9470 step-up for a person's token on the management plane (/mcp and REST): the authentication class a critical action needs. aal2 (or mfa) for a second factor, aal1 for any sign-in, or the full class (urn:cbox-id:aal2). Short of it, the call is answered 401 insufficient_user_authentication. Management keys are never asked. Read from config/api.php. unset (off) aal2 to require that an agent's session was signed in with a second factor. See agents and MCP.
CBOX_ID_MCP_STEP_UP_MAX_AGE The same, for how recent the sign-in must be, in seconds. Either variable may be set alone. Read from config/api.php. unset (off) 900
CBOX_ID_REQUIRE_PAR Require Pushed Authorization Requests (RFC 9126) — clients must push params server-side instead of via the front channel. false Set true to harden the authorization endpoint for FAPI-style deployments.
CBOX_ID_WEBAUTHN_USER_VERIFICATION Require user verification (PIN/biometric) during the passkey ceremony. true Rarely changed; leave on.
CBOX_ID_EMBED_ENTITLEMENTS Embed entitlement claims into issued tokens. true Disable if consumers resolve entitlements out-of-band.
CBOX_ID_ACCESS_TOKEN_TTL Lifetime of an issued access token, in seconds. 900 (15m) Shorten for higher assurance; lengthen only with a good reason — the refresh token, not a long access token, is the right way to keep a session alive.
CBOX_ID_DECISIONS_MAX_BATCH Maximum number of decisions a single POST /oauth/decisions request may ask for. 50 Raise only if a resource server genuinely batches more; the endpoint is a hot path.
CBOX_ID_CIBA_TTL_SECONDS How long a CIBA (client-initiated backchannel) authentication request stays pending. 300 Match your out-of-band approval window.
CBOX_ID_CIBA_POLL_INTERVAL The interval handed back to a CIBA client, in seconds. 5 Raise to reduce polling load.
CBOX_ID_AUTHORIZATION_ENDPOINT_PATH The path of the interactive authorization endpoint, joined to each environment's own issuer so every tenant advertises it on its own host. /oauth/authorize Never, in this app: it is set in config/cbox-id.php because it describes these routes.
CBOX_ID_AUTHORIZATION_ENDPOINT An absolute authorization-endpoint URL, used only when no path is configured. (none) Avoid. An absolute URL pins every environment to one host, which a client that checks RFC 9207 iss will reject.

Webhooks

Delivery is queued, not inline — see the note on QUEUE_CONNECTION under Standard Laravel keys. Without a running worker nothing is delivered and nothing errors.

Variable What it does Default When to change
CBOX_ID_WEBHOOKS_VERIFY_URL SSRF-guard + verify webhook target URLs before delivery. true Keep on. Only relax in isolated test setups.
CBOX_ID_WEBHOOKS_MAX_ATTEMPTS Max delivery attempts before a webhook is marked failed. 12 Raise/lower to match your retry tolerance.
CBOX_ID_WEBHOOKS_SCHEDULE_RETRIES Let the scheduler re-drive failed deliveries. true Requires schedule:run from cron; keep on in production.
CBOX_ID_WEBHOOKS_QUEUE_CONNECTION Queue connection the delivery job is dispatched on, so webhook egress can be isolated from the rest of the app's work. (none — the app default connection) Set when you run a dedicated worker fleet for egress. The named connection must be one a worker is actually consuming.
CBOX_ID_WEBHOOKS_QUEUE Queue name within that connection. (none — the default queue) As above.
CBOX_ID_WEBHOOKS_RETRY_LIMIT How many pending/failed deliveries one webhook retry sweep picks up. 50 Raise if a large backlog drains too slowly; each sweep runs every scheduler tick.
CBOX_ID_WEBHOOKS_STRANDED_AFTER_SECONDS How long a delivery may sit Pending before the retry sweep treats it as stranded (its worker died) and re-drives it. 900 (15m) Lower for faster rescue, but keep it comfortably above your longest legitimate delivery.
CBOX_ID_WEBHOOKS_CB_FAILURE_THRESHOLD Consecutive failures against one endpoint before its circuit breaker opens. 5 Raise for flaky-but-recovering endpoints.
CBOX_ID_WEBHOOKS_CB_COOLDOWN_SECONDS How long an open breaker stays open before a trial delivery. 300 Raise to back off harder from a dead endpoint.

Log streams (SIEM)

Log streams need nothing configured to ship to an HTTP collector, Datadog, Google Cloud Storage, or an Amazon S3 bucket with the customer's own access key. These three are only for S3 through an assumed role, where the customer stores no secret with you: their IAM role trusts your AWS principal and requires the stream's external ID, and the platform calls sts:AssumeRole with the identity below.

Variable What it does Default When to change
SIEM_AWS_ACCESS_KEY_ID Access key ID of the platform's own IAM user, used only to sign sts:AssumeRole. A secret reference in cbox.yaml. (none) Set to offer Assume an IAM role on S3 streams. Without it the option is shown as unavailable and the API refuses role_arn (assumed_role_unavailable).
SIEM_AWS_SECRET_ACCESS_KEY That user's secret access key. A secret reference in cbox.yaml, never a value. (none) With the key ID. Rotate it in IAM and here; streams are unaffected.
SIEM_AWS_PRINCIPAL_ARN The ARN of that IAM user (arn:aws:iam::<account>:user/<name>). Not a secret: it is the Principal in the trust policy the console and the Admin Portal hand your customers. (none — the policy shows a placeholder) Set together with the key, or every customer has to ask you for it.

The IAM user needs exactly one permission and nothing else, because it can do nothing on its own — every write happens as the customer's role:

{
  "Version": "2012-10-17",
  "Statement": [{ "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "*" }]
}

Narrow Resource to arn:aws:iam::*:role/<a naming convention> if you ask customers to name their roles one way. The SIEM package's other settings (SIEM_AWS_STS_ENDPOINT, SIEM_AWS_STS_REGION, SIEM_AWS_ROLE_DURATION, SIEM_GCS_TOKEN_URI, SIEM_DATADOG_SOURCE, SIEM_DATADOG_SERVICE) default correctly for AWS, Google and Datadog; see laravel-siem's configuration.

Domain-event outbox

Every subscriber in the platform hangs off the outbox relay — webhooks, usage metering, outbound provisioning, token revocation on role change. The relay itself is scheduled (see Scheduled work); these tune it.

Variable What it does Default When to change
CBOX_ID_EVENTS_RELAY_LIMIT Events claimed per relay run. 100 Raise for high event volume; a single run should still finish well inside its cadence.
CBOX_ID_EVENTS_RELAY_CADENCE How often the relay runs: every_minute, every_two_minutes, every_five_minutes, every_ten_minutes, every_fifteen_minutes, every_thirty_minutes, hourly. An unrecognized value silently falls back to every_minute. every_minute Slow it down only on a very quiet instance — every downstream effect inherits this latency.
CBOX_ID_EVENTS_RECLAIM_AFTER_SECONDS How long a claimed-but-unfinished event waits before another relay run may reclaim it (the worker died mid-flight). 300 Lower for faster recovery; keep above the slowest legitimate subscriber.
CBOX_ID_EVENTS_MAX_ATTEMPTS Attempts before an event is given up on. 12 Rarely.
CBOX_ID_EVENTS_BACKLOG_WARNING_THRESHOLD Unrelayed-event count at which the health/doctor output starts warning. 1000 Tune to your normal volume so the warning stays meaningful.

Outbound provisioning (SCIM push)

Variable What it does Default When to change
CBOX_ID_PROVISIONING_VERIFY_URL SSRF-guard + verify a directory's SCIM base URL before pushing to it. true Keep on.
CBOX_ID_PROVISIONING_MAX_ATTEMPTS Attempts per provisioning operation before it is marked failed. 12 Raise/lower to match the target's reliability.
CBOX_ID_PROVISIONING_BATCH_LIMIT Operations drained per cbox-id:provisioning:drain run. 50 Raise for large directory syncs.
CBOX_ID_PROVISIONING_CB_FAILURE_THRESHOLD Consecutive failures against one target before its breaker opens. 5 As for webhooks.
CBOX_ID_PROVISIONING_CB_COOLDOWN_SECONDS How long that breaker stays open. 300 As for webhooks.

Inbound federation

Variable What it does Default When to change
CBOX_ID_FEDERATION_VERIFY_URL SSRF-guard + verify the URLs on an SSO connection (issuer, endpoints, JWKS) before they are fetched. Connections are tenant-supplied, so this is the guard against a customer pointing the server at your internal network. true Keep on. Relax only in an isolated test network.

External actions (hooks)

Synchronous HTTP callouts during a flow — a host-owned decision point inside token issuance and login.

Variable What it does Default When to change
CBOX_ID_ACTIONS_VERIFY_URL SSRF-guard + verify an action endpoint's URL before calling it. true Keep on.
CBOX_ID_ACTIONS_TIMEOUT Total request timeout, in seconds. 3 Keep small — this sits inline on the token/login path.
CBOX_ID_ACTIONS_CONNECT_TIMEOUT Connect timeout, in seconds. 2 As above.
CBOX_ID_ACTIONS_CACHE_TTL How long the active action set is cached, in seconds. 60 Lower for faster propagation of a hook change; the lookup is on the hot path.
CBOX_ID_ACTIONS_FAIL_OPEN What happens when an action times out, errors, or returns an unparseable body. false — fails closed (the request is denied) Set true only if you have accepted that a hook outage stops being a control. A security control that fails open is not a control.

Access control (RBAC and app manifests)

Variable What it does Default When to change
CBOX_ID_ACCESS_CONTROL_DRIVER Which authorization backend the platform and its token claims read from. builtin — the package's hierarchy-aware RBAC (its schema is loaded and its services bound). external — bring your own: the built-in tables and migrations are not loaded and the checker falls back to a refusing default until you bind an adapter. builtin Only when replacing RBAC wholesale. external is deny-by-default until your adapter is bound — nothing authorizes in between.
CBOX_ID_MANIFEST_VERIFY_URL SSRF-guard + verify the manifest URL an application publishes its roles/permissions at, before it is fetched. The URL is supplied by the application owner, so this is what stops it pointing at link-local or internal addresses. true Keep on. false removes the guard entirely.
CBOX_ID_MANIFEST_FETCH_TIMEOUT Seconds to wait for a manifest fetch. 10 Raise for slow app hosts.

Undeclared key: cbox-id.access_control.schedule. The hourly manifest re-pull (cbox-id:app-manifests:sync) is registered only when this config value is true, and it is read with a hard-coded true fallback — but it is declared in neither config file and has no CBOX_ID_ variable, so .env cannot turn it off. To disable the hourly pull you must set cbox-id.access_control.schedule to something other than true in config/cbox-id.php itself. Note the comparison is strict: the string "false" is not true, so any non-true value disables it.

One-time passcodes (OTP)

Variable What it does Default When to change
CBOX_ID_OTP_CODE_LENGTH Digits in a generated code. Clamped to 6–10 — a smaller value is raised to 6, a larger one lowered to 10. 6 Raise for higher-assurance flows.
CBOX_ID_OTP_TTL_SECONDS How long a code stays valid. 300 (5m) Shorten for tighter windows; too short and legitimate mail delivery loses the race.
CBOX_ID_OTP_MAX_ATTEMPTS Wrong guesses against one code before it is burned. 5 Lower to harden against guessing.
CBOX_ID_OTP_ISSUE_MAX Codes one client may request per issue window. 5 Lower to blunt mail/SMS flooding.
CBOX_ID_OTP_ISSUE_RECIPIENT_MAX Codes one recipient may be sent per issue window. 10 As above.
CBOX_ID_OTP_ISSUE_WINDOW Length of the issue window, in seconds. 3600 (1h) Pair with the two limits above.
CBOX_ID_OTP_VERIFY_MAX Verification attempts one client may make per verify window. 20 Lower to harden.
CBOX_ID_OTP_VERIFY_RECIPIENT_MAX Verification attempts against one recipient per verify window. 15 As above.
CBOX_ID_OTP_VERIFY_WINDOW Length of the verify window, in seconds. 900 (15m) Pair with the two limits above.
CBOX_ID_OTP_EMAIL_SUBJECT Subject line of the code email. Your verification code Match your product voice.
CBOX_ID_OTP_EMAIL_FROM_ADDRESS From address for the code email. (none — falls back to the app's MAIL_FROM_ADDRESS) Set to send codes from a different, well-aligned sender than the rest of your mail.
CBOX_ID_OTP_EMAIL_FROM_NAME From name for the code email. (none — falls back to MAIL_FROM_NAME) As above.

SAML identity provider (this platform AS the IdP)

Published in /sso/saml/idp/metadata and consumed by every downstream service provider that federates to you.

Variable What it does Default When to change
CBOX_ID_SAML_IDP_ENTITY_ID The IdP entityID in published metadata. (derived from the issuer) Set only if an existing SP already pins a different entity ID. Changing it after SPs are live breaks their trust config.
CBOX_ID_SAML_IDP_LOGIN_URL The SingleSignOnService location advertised in metadata. (derived — the app's /sso/saml/idp/sso) Set only when a proxy serves that endpoint at a different URL.
CBOX_ID_SAML_IDP_WANT_AUTHN_REQUESTS_SIGNED Advertises WantAuthnRequestsSigned in metadata. Only a real boolean is honoured. A value from .env arrives as a string, is not a boolean, and is therefore ignored — the derived behaviour applies instead: true only when every registered SP is itself marked as wanting signed requests, false if any is not (and false when none are registered). (unset — derived) To force it, set 'want_authn_requests_signed' => true (or false) as a real boolean in config/cbox-id.php. Setting the environment variable alone changes nothing.

Data retention (pruning)

cbox-id:prune sweeps expired rows out of the operational tables. Values are retention in days, counted from each table's own expiry/creation column, and the sweep deletes in chunks so a first run on a never-swept table is not one enormous transaction.

Variable What it does Default When to change
CBOX_ID_PRUNE_TIME Daily run time, HH:MM, in the app timezone. 03:10 Move it away from your backup window.
CBOX_ID_PRUNE_CHUNK Rows deleted per batch. 1000 Lower on a busy primary to shorten lock windows.
CBOX_ID_PRUNE_DPOP_PROOFS Retention for dpop_proofs (replay-guard jti records). 1 Rarely — they are worthless once the proof window has passed.
CBOX_ID_PRUNE_CONSUMED_ASSERTIONS Retention for consumed_assertions (SAML replay guard). 1 As above.
CBOX_ID_PRUNE_AUTHORIZATION_CODES Retention for oauth_authorization_codes. 1 Rarely — codes are single-use and short-lived.
CBOX_ID_PRUNE_ACCESS_TOKENS Retention for oauth_access_tokens. 7 Raise if you introspect historical tokens for support.
CBOX_ID_PRUNE_REFRESH_TOKENS Retention for oauth_refresh_tokens. 30 Keep at least as long as your refresh-token lifetime.
CBOX_ID_PRUNE_EVENTS Retention for the domain-event outbox (events). 30 Raise if you replay events for debugging.
CBOX_ID_PRUNE_AUTH_SESSIONS Retention for auth_sessions. 30 Match your session forensics needs.
CBOX_ID_PRUNE_USAGE_MARKERS Retention for usage_metered_events — the idempotency markers behind usage metering. 30 Raise only if your billing reconciliation window is longer than 30 days.
CBOX_ID_PRUNE_WEBHOOK_DELIVERIES Retention for webhook_deliveries. 30 Raise if customers debug delivery history.
CBOX_ID_PRUNE_PROVISIONING_OPERATIONS Retention for provisioning_operations. 30 As above.
CBOX_ID_PRUNE_UNUSED_DYNAMIC_CLIENTS Removes self-registered clients (RFC 7591) nobody has used for this many days and that hold no live refresh token. A client an administrator registered is never swept. 30 (this app; the framework leaves it off) Raise if people come back to an agent after longer breaks; set it empty to keep every registration.

An empty value disables pruning for that table — it does not fall back to the default. CBOX_ID_PRUNE_EVENTS= in .env resolves to '', and the pruner treats '' (like null and false) as "retention disabled" and skips the table entirely, forever. Only a numeric value sets a retention; a non-numeric, non-empty value falls back to the built-in default. If you mean the default, delete the line — do not leave it blank. Check with php artisan cbox-id:prune --dry-run, which reports each table as pruned or skipped.

audit_logs is deliberately not prunable and has no key. The audit trail is the evidentiary record; it is retained by export/archival policy, not by a sweep that can be misconfigured into deleting it.

Scheduled work

Every one of these needs schedule:run (or schedule:work) actually running — see Deployment. They are on by default; setting one to false removes that command from the schedule with no other signal.

Variable Schedules Default When to change
CBOX_ID_EVENTS_SCHEDULE_RELAY cbox-id:events:relay — the outbox relay every subscriber depends on. true Only when an external supervisor runs the relay itself. Turning it off with nothing else driving it silently disables webhooks, usage metering, outbound provisioning and role-change revocation.
CBOX_ID_PROVISIONING_SCHEDULE cbox-id:provisioning:drain — the outbound SCIM outbox. true As above.
CBOX_ID_AUDIT_STREAMING_SCHEDULE cbox-id:audit-streams:pump — SIEM stream delivery. true As above.
CBOX_ID_GOVERNANCE_SCHEDULE The governance sweeps (access reviews and their reminders/expiries). true Off only if you do not use governance campaigns.
CBOX_ID_PRUNE_SCHEDULE The daily cbox-id:prune sweep (see Data retention). true Off only if you prune out-of-band.
CBOX_ID_WEBHOOKS_SCHEDULE_RETRIES The webhook retry sweep — see Webhooks. It is a scheduled closure, not an artisan command, though schedule:list shows it as cbox-id:webhooks:retry. true Keep on in production.
CBOX_ID_COMPLIANCE_SCHEDULE_EXPORT id-compliance:export every five minutes — ships new audit entries to the configured sink (SIEM / JSONL archive). Registers only when the compliance module is active. true Off only if something else drives the export. With it off and a sink configured, the module reports itself active, the backlog grows, and nothing is ever shipped.
CBOX_ID_COMPLIANCE_SCHEDULE_RETENTION id-compliance:retention daily — signs a fresh checkpoint on every audit chain so archived history stays externally verifiable. Never deletes entries. Registers only when the compliance module is active. true Off only if you anchor chains out-of-band.

The hourly app-manifest re-pull is scheduled too, but has no environment variable — see the undeclared-key note under Access control.

Token vault, usage metering and user API tokens

Variable What it does Default When to change
CBOX_ID_VAULT_LEASE_TTL Default lifetime of a token-vault lease, in seconds — how long a caller may hold a released upstream credential. 300 (5m) Keep short; a lease is a live third-party credential.
CBOX_ID_USAGE_ENABLED Whether usage is metered at all. Metering feeds plan gates and billing projections. true Set false only on an install that has no billing and no plan gates. With it off, anything reading usage sees zero.
CBOX_ID_USER_API_TOKEN_TTL_DAYS Default lifetime, in days, of a user API token (cbid_pat_…) minted without an explicit expiry. 90 Shorten for tighter credential hygiene. A non-numeric value falls back to the built-in default.

Trusted devices (push)

The phone-as-authenticator module: approval pushes and security alerts. Off by default — a deployment with no mobile app has no devices to notify. See Trusted devices for how the pieces fit together.

Variable What it does Default When to change
CBOX_ID_DEVICES_ENABLED Master switch for the console pages, the device API and the CIBA push decorator. false Turn on when you are running the authenticator app. Safe to enable before configuring a transport — pushes are recorded and dropped, and the console still shows the delivery history.
CBOX_ID_DEVICES_TRANSPORT How a push leaves the building: none or fcm. none Set fcm for real delivery. FCM covers Android natively and iOS by relaying to APNs, which is why there is no separate APNs driver.
CBOX_ID_DEVICES_FCM_CREDENTIALS Absolute path to the Google service-account JSON used to mint FCM tokens. (empty) Required for fcm. This key can push to every device you have ever enrolled — server only, never bundled into the app. If it or the project id is missing the transport stays on the null driver rather than failing at send time: a misconfigured push must not break a login.
CBOX_ID_DEVICES_FCM_PROJECT_ID The Firebase project id. (empty) Required for fcm.
CBOX_ID_DEVICES_FCM_TIMEOUT Connect/read timeout in seconds for the FCM call. 10 Lower it if you run pushes inline on QUEUE_CONNECTION=sync, where this bounds the CIBA request itself.
CBOX_ID_DEVICES_MAX_ATTEMPTS Retries before a notification dead-letters as Exhausted. Backoff is min(60, 2^attempt) minutes. 12 Rarely. Governs transient failures only — a permanent FCM error (UNREGISTERED, INVALID_ARGUMENT) retires the device token on the first attempt instead.
CBOX_ID_DEVICES_STRANDED_AFTER_SECONDS How long a Pending notification waits before the sweep presumes its queue job is lost and re-enqueues it. 900 Rarely — and never alone. The delivery job's unique-lock window is deliberately the same value; raising one without the other wedges the rescue shut.
CBOX_ID_DEVICES_RETRY_LIMIT How many due notifications one sweep re-enqueues. 50 Raise on a large fleet where the sweep cannot keep up; it bounds the work one scheduler tick creates.
CBOX_ID_DEVICES_CB_FAILURE_THRESHOLD Consecutive failures that open a device's circuit breaker. 5 Rarely. State lives on the device row, not in cache, so it survives a flush and is visible in the console.
CBOX_ID_DEVICES_CB_COOLDOWN_SECONDS How long the breaker stays open before a single half-open probe. 300 Rarely. While open, notifications are parked without being charged an attempt — the trip is the device's fault, not the notification's.
CBOX_ID_DEVICES_QUEUE_CONNECTION Queue connection for delivery jobs. (default connection) Set it. Approval pushes race a 300-second CIBA TTL and should not share a worker pool with slow bulk work.
CBOX_ID_DEVICES_QUEUE Queue name for delivery jobs. (default queue) As above.
CBOX_ID_DEVICES_ALERT_TTL_SECONDS How long a security alert stays worth delivering. 86400 Rarely. This deadline is what stops one permanently soft-failing handset accumulating Failed rows that occupy every retry slot and starve other tenants' approvals. Approvals take their deadline from the CIBA request's TTL instead.
CBOX_ID_DEVICES_RETENTION_DAYS How long settled notification rows are kept for the console's delivery history. 30 This table grows with traffic, not tenants — one row per enrolled device per alerted event — so the prune is not optional. Only terminal rows are pruned.
CBOX_ID_DEVICES_RATE_LIMIT Per-minute request budget for the device API, keyed on a fingerprint of the presented token (so, per user). 60 Raise for a chatty app. Keying on client_id instead would put every mobile user in one bucket and let one busy account throttle the fleet.
CBOX_ID_DEVICES_CIBA_CLIENT_ALLOWLIST Comma list of client ids that may cause a push via CIBA. Empty means every client holding the CIBA grant may. (empty) Set it to narrow the permissive default. Enforced as a refusal to notify, not a log line — an attacker who can spray approval prompts at a phone is attacking the human-in-the-loop that CIBA exists for. The CIBA request still succeeds and the client can poll; it just produces no push.
CBOX_ID_DEVICES_INCLUDE_REQUEST_ID_IN_PUSH Whether the approval request id travels in the FCM data payload, so a notification tap deep-links to the right approval. true Set false to keep the id off Google's and Apple's infrastructure. The id is an unguessable ULID conferring no capability on its own — approving still needs a DPoP-bound token whose subject matches — so this is a minor metadata trade against deep-link precision when two approvals are waiting.

REST management API rate limits

Buckets are keyed on the API key, not the caller's IP — a customer whose CI egresses through a shared NAT gets its own allowance instead of sharing one with every other tenant behind that address. Each value is requests per minute, per credential.

Variable What it does Default When to change
CBOX_ID_API_RATE_LIMIT_ORGANIZATION Budget for the organization plane (/api/v1/workspace/*). 120 Raise for an organization driving many projects/environments from CI.
CBOX_ID_API_RATE_LIMIT_ENVIRONMENT Budget for the environment plane (/api/v1/organizations, /api/v1/users). 240 Raise for bulk provisioning; this is the plane a Terraform/SDK sync hammers.
CBOX_ID_API_RATE_LIMIT_VAULT Budget for the token-vault endpoints. 120 Rarely.
CBOX_ID_API_RATE_LIMIT_APPS Budget for the app-manifest push endpoint. 60 Rarely — a manifest push is a deploy-time event.
CBOX_ID_API_RATE_LIMIT_IP_MULTIPLIER Abuse backstop, as a multiple of the plane budget, applied per source IP. Bounds a flood of distinct invalid credentials, which a per-credential bucket alone cannot. 10 Raise if one egress address genuinely fronts more than ~10 busy tenants. 0 disables the backstop.

A throttled request returns 429 with the standard { "error": "rate_limited", "message": … } envelope plus Retry-After and the X-RateLimit-* headers.

Risk scoring

Variable What it does Default When to change
RISK_MODE cboxdk/laravel-risk operating mode: monitor (score and log only) or an enforcing mode (challenge/reject). monitor Risk blocking is OFF by default — scores are recorded but nothing is challenged or rejected until you switch this to an enforcing mode. Change it once you've reviewed the scores your traffic produces.

Reverse proxy

Variable What it does Default When to change
TRUSTED_PROXIES Which proxies' X-Forwarded-* headers to trust (* = trust all, or a comma-separated CIDR list). Correct forwarding makes the audit trail record the real client IP, keys rate limiting on it, and gets the issuer/cookie host right. (empty — trust nothing) Set this on any deployment behind a proxy or load balancer. Left empty, X-Forwarded-* is ignored: every audit entry records the proxy's address instead of the client's, and every per-IP rate limit — signup, the API backstop — collapses into one bucket shared by the whole platform. * is safe only when the app is reachable exclusively through your ingress (a k8s pod behind Traefik/Cloudflare, or a managed platform that fronts every request). If the app is directly reachable, pin this to your proxy CIDR(s).

Security posture (defaults you should keep)

These make the deployment safe to expose. cbox-id:doctor flags THREE of them in production — APP_DEBUG, SESSION_SECURE_COOKIE and SESSION_ENCRYPT. The rest of this table is yours to hold: nothing checks them for you, and a green doctor is not a statement about SESSION_SAME_SITE, HASH_DRIVER or SESSION_DRIVER.

Variable Ship as Why
APP_DEBUG false Debug pages leak stack traces, config and secrets.
APP_ENV production Enables the hardening checks and disables dev affordances.
SESSION_SECURE_COOKIE true Cookies only over HTTPS — this is a login surface.
SESSION_ENCRYPT true Encrypt session payloads at rest in the store.
SESSION_SAME_SITE strict Mitigates CSRF (relax to lax only if a cross-site OIDC redirect flow needs it).
HASH_DRIVER argon2id Memory-hard, side-channel-resistant password hashing (this app's default, overriding the framework's bcrypt default). Requires sodium/argon2 support.
SESSION_DRIVER redis (recommended) Central, revocable sessions; enables sign-out-everywhere and idle timeout.

Standard Laravel keys

The usual APP_KEY, DB_*, REDIS_*, MAIL_* apply as in any Laravel app — but two of them carry more weight here than the framework defaults suggest:

  • CACHE_STORE should be redis in anything but a throwaway install. The platform leans hard on the cache: JWKS and verification keys, host → environment resolution on every request, the entitlement hot path, and the active inline-hook set on every token mint. On the database store each of those becomes a query against the cache table — the very round trip the caching exists to remove — so database quietly undoes it and adds write contention on top. database is the zero-dependency default for a first php artisan serve, and nothing more. docker-compose.yml already uses Redis.
  • QUEUE_CONNECTION is not cosmetic. Webhook delivery and back-channel logout are dispatched to the queue, so a deployment without a running queue manager (php artisan queue:autoscale) delivers nothing and raises no error — deliveries simply sit Pending until the retry sweep re-drives them into the same empty queue. See Queue workers for the process a real deployment runs, and CBOX_ID_WEBHOOKS_QUEUE_CONNECTION / CBOX_ID_WEBHOOKS_QUEUE above if you isolate egress onto its own queue: the workers follow it.

Queue workers

The queue manager (cboxdk/laravel-queue-autoscale) and the job monitor (cboxdk/laravel-queue-monitor). Most of their settings are values in config/queue-autoscale.php and config/queue-monitor.php, deliberately not environment variables; these are the ones a deployment sets. See Queue workers.

Variable Purpose Default Set it when
QUEUE_AUTOSCALE_MAX_TOTAL_WORKERS Hard cap on queue:work processes per host. 2 The workers get more memory than a 512 MB instance shared with the web tier.
QUEUE_AUTOSCALE_CLUSTER_ENABLED Let several managers share one set of queues through Redis. false More than one instance runs the manager. cbox-id:doctor then fails a per-process cache or session store.
CBOX_ID_REPLICAS How many web replicas the deployment runs — the number its manifest gives the platform (replicas: in cbox.yaml), told to the app, which cannot count its siblings. Changes nothing at runtime. 1 Always, beside replicas:. Above 1, cbox-id:doctor fails a file, array or apc cache or session store instead of warning: every replica would hold its own rate limits, single-use tokens, replay guards and sessions.
QUEUE_AUTOSCALE_ENABLED Switch the manager off. The queue_workers health check then judges only the backlog. true You supervise plain queue:work processes yourself instead.
QUEUE_METRICS_STORAGE Where the metrics the manager scales on are kept. redis Leave it.
QUEUE_MONITOR_MAX_ROWS Most job rows the monitor keeps, on top of its 7-day window. 100000 A very busy install.
QUEUE_MONITOR_API_ENABLED The monitor's REST API, behind the operator session. false You need it; the dashboard does not.

APP_KEY is required and distinct from CBOX_ID_CRYPTO_KEY — the former protects Laravel's own encryption/cookies, the latter protects the identity platform's sealed secrets. Both must be backed up; neither is recoverable if lost.

Signing keys are not env vars

Signing keys live in the database, are minted on install/first use, and are rotated with cbox-id:keys:rotate (see Operations). The public half is published at /.well-known/jwks.json.

Optional subsystems

  • Risk scoring (cboxdk/laravel-risk) — bot/abuse scoring on signup/login. Ships in monitor mode (RISK_MODE, above); see the package docs to enforce.
  • Social/enterprise SSO — Socialite + connection config per organization; managed from the admin console, not env.

Where to go next

  • Operations — key backup/rotation, upgrades, break-glass.
  • Framework config reference: config/cbox-id.php in the cboxdk/laravel-id package (installation guide).