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 master key for envelope encryption of sealed secrets (signing keys, connection creds). (none — generated by cbox-id:install) Set once at install. Never rotate casually — it decrypts existing sealed data. Back it up separately from the DB; losing it makes sealed secrets unrecoverable. See Operations.
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 account — 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 account, 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. 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.

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.

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.

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) mode. Controls whether clients (e.g. MCP clients) may self-register: disabled, protected (initial access token required) or open. disabled Enable when you need self-registration; pair with CBOX_ID_DCR_INITIAL_ACCESS_TOKEN for gated registration.
CBOX_ID_DCR_INITIAL_ACCESS_TOKEN Bearer token required to register a client when DCR is gated. (none) Set when DCR is enabled but should not be open.
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.

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 (inline 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. falsefails 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.

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/organization/*). 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 any that regress in production.

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 is dispatched to the queue, so a deployment without a running queue:work delivers nothing and raises no error — deliveries simply sit Pending until the retry sweep re-drives them into the same empty queue. See Deployment for the processes a real deployment runs, and CBOX_ID_WEBHOOKS_QUEUE_CONNECTION / CBOX_ID_WEBHOOKS_QUEUE above if you isolate egress onto its own worker fleet.

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).