Configuration

Publish the config, then drive it with environment variables:

php artisan vendor:publish --tag=laravel-iam-client-config

All keys

Key Env Default Meaning
mode IAM_CLIENT_MODE local Transport: local = in-process PDP; http = remote Admin API. Any value ≠ http selects local.
http.base_url IAM_CLIENT_BASE_URL Versioned API root, e.g. https://iam.example.com/api/iam/v1. The client appends /decisions/check.
http.token IAM_CLIENT_TOKEN Bearer token for the Admin API. Omitted from the request when null.
http.client_id IAM_CLIENT_ID client_credentials auth; the SDK obtains/renews the token itself. Precedence over a static token.
http.client_secret IAM_CLIENT_SECRET The rotatable secret. An auto-rotated secret is cached encrypted with a bounded TTL (IAM-25).
http.private_key IAM_CLIENT_PRIVATE_KEY private_key_jwt (RFC 7523): ES256 PEM (inline or path). Precedence over client_secret.
http.private_key_kid IAM_CLIENT_PRIVATE_KEY_KID kid of the public key registered in IAM’s JWKS.
http.oauth_url IAM_CLIENT_OAUTH_URL Token endpoint; derived from base_url when unset.
http.allow_insecure IAM_CLIENT_ALLOW_INSECURE false IAM-39: allow credentials over http://. Fail-closed by default (non-https, except localhost, gets no secret). Dev only.
http.timeout 5 Guzzle request timeout (seconds).
subject_type user Subject type sent in every decision query.
default_application IAM_CLIENT_APP Default application when a call doesn’t pass one.
default_organization IAM_CLIENT_ORG Default organization (tenant) when a call doesn’t pass one.
cache.enabled true Wrap the transport in CachingDecider.
cache.ttl 30 Decision cache TTL in seconds. <= 0 disables caching even when enabled.
cache.store null Laravel cache store name. null = default store.
gate.enabled true Register the Gate::before adapter.
gate.intercept namespaced namespaced = only abilities with :; all = every ability.
gate.app_keys IAM_CLIENT_APP_KEYS [] (all namespaced) IAM-40: comma-separated app prefixes; intercept only these app: namespaces so third-party abilities aren’t claimed. Each entry trimmed.

The published file

return [
    'mode' => env('IAM_CLIENT_MODE', 'local'),

    'http' => [
        'base_url'        => env('IAM_CLIENT_BASE_URL'),   // e.g. https://iam.example.com/api/iam/v1
        'token'           => env('IAM_CLIENT_TOKEN'),        // static Bearer for the Admin API
        'client_id'       => env('IAM_CLIENT_ID'),           // client_credentials (self-renewing token)
        'client_secret'   => env('IAM_CLIENT_SECRET'),       // rotatable; auto-rotated copy cached encrypted (IAM-25)
        'private_key'     => env('IAM_CLIENT_PRIVATE_KEY'),  // private_key_jwt: ES256 PEM or path
        'private_key_kid' => env('IAM_CLIENT_PRIVATE_KEY_KID'),
        'oauth_url'       => env('IAM_CLIENT_OAUTH_URL'),     // else derived from base_url
        'timeout'         => 5,
        'allow_insecure'  => (bool) env('IAM_CLIENT_ALLOW_INSECURE', false),  // IAM-39: http:// only in dev
    ],

    'subject_type'         => 'user',
    'default_application'  => env('IAM_CLIENT_APP'),
    'default_organization' => env('IAM_CLIENT_ORG'),

    'cache' => [
        'enabled' => true,
        'ttl'     => 30,     // seconds
        'store'   => null,   // null = default store
    ],

    'gate' => [
        'enabled'   => true,
        'intercept' => 'namespaced',  // or 'all'
        'app_keys'  => [],            // IAM-40: e.g. ['warehouse','billing']; empty = all namespaced
    ],
];

Example .env blocks

Remote (http)
Same app (local)
IAM_CLIENT_MODE=http
IAM_CLIENT_BASE_URL=https://iam.example.com/api/iam/v1
IAM_CLIENT_TOKEN=${IAM_SERVICE_TOKEN}
IAM_CLIENT_APP=billing
IAM_CLIENT_ORG=org_acme
IAM_CLIENT_MODE=local
IAM_CLIENT_APP=billing
IAM_CLIENT_ORG=org_acme

Notes

There is no fail_open key

The transport is always fail-closed: an unreachable PDP denies. Tolerating an outage is a conscious
application choice, not a config setting.

Use https — credentials are fail-closed on http (IAM-39 / IAM-39b)

No credential travels over plain http://. The guard (TransportGuard) covers every credential-bearing
path: the client_credentials and private_key_jwt token endpoints (oauth_url), the decision call to
base_url (which carries the Bearer in every mode, including the static token), and the
iam:manifest:push command. A non-https URL (except loopback localhost / 127.0.0.1 / ::1) yields no
token / a deny, never a leak. Redirects are also disabled on these requests, so a 30x https→http
downgrade can’t replay the body over cleartext. http.allow_insecure=true (IAM_CLIENT_ALLOW_INSECURE)
lifts this for local dev only — so set both oauth_url and base_url to https in production. An
auto-rotated secret is additionally cached encrypted at rest (IAM-25).

Cache TTL is your revocation latency

A short TTL (default 30s) bounds how long a revoked grant keeps being honored on each node. explain queries
are never cached regardless. See Cache decisions.

Defaults keep call sites terse

Set default_application / default_organization once (via IAM_CLIENT_APP / IAM_CLIENT_ORG) and most
calls won’t need to pass them — they’re inherited unless overridden per call. See
ABAC context & ReBAC resources.

Turn the Gate off for shadow mode

Set gate.enabled = false while the spatie bridge runs in shadow
mode, so the adapter’s enforcement doesn’t pollute decision diffing.

Cache after changing config

In production with config:cache, run php artisan config:clear (or re-cache) after editing .env so the
new transport/cache/gate settings take effect.

See also