Gate One configuration
Generated from schemas/api-routes.schema.json by scripts/gen-config-docs. Do not edit this page: edit the schema or the generator.
The api-routes.json file consumed by the gate-one binary. Relative paths (certificates, keys, plugin artefacts, WAL directories) are resolved from the configuration file.
Fields
| Field | Type | Required | Description |
|---|---|---|---|
no | Platform-wide response hardening, emitted by the gateway on EVERY response it serves (proxied or generated: health, stats, 401/403, MCP metadata, PWA assets, CSP collector). Declaring the block applies the secure defaults; each field can be tightened or disabled. Referrer-Policy is only set when the response does not already carry one (a proxified app’s stricter policy wins). Absent = nothing (an install whose edge proxy already sets them can opt out). | ||
no | Optional offline signed licence (Ed25519), one token per period. Absent, invalid or expired = evaluation/personal mode: the gateway keeps serving; the user badge shows a notice. The private signing key NEVER ships (only publicKey is embedded). | ||
no | Authentication provider. | ||
array of object | no | Named authentication providers for per-route binding. Mutually exclusive with the legacy singular authProvider. Each item uses the same fields as authProvider plus id/cookieName; a route selects one with auth.provider. | |
string | no | Provider id used by routes that do not set auth.provider. Required when more than one authProviders entry is declared. | |
string | no | Global fallback for a bare visit to a login host root (302), preserving the query string. A provider’s own defaultAppUrl wins for its login host. Email verification/account links must carry a return target; use defaultAppUrl only as an operational fallback. Point it at a neutral page (e.g. your landing), not at one application. | |
no | LEGACY platform IdP block. When authProvider is absent, it is translated into an http provider (auth-bridge) at load; existing configs keep working. New configurations should use authProvider. | ||
no | Internal per-user credential broker. Required when a route declares upstream.tokenBroker; a configured block also keeps the Services page (/connections: pause/resume/erase) available when no route delegates. The shared secret is read from the named environment variable at load (never from this file) and is never logged. | ||
no | Optional defaults shared by every MCP access unit. | ||
map of object | no | Named groups of MCP services exposed with their own access rights (resource/scope/identity) and per-member tool filters. A member endpoint is set.host + set.endpoint + member.path. | |
no | Optional signed publisher feed: compatible Gate One versions, external components and plugins, with their revocation status. Loaded and verified at startup; the operator’s trust store stays authoritative. Absent = no feed is consulted. | ||
no | Platform-wide Cloudflare Turnstile bot check for form pages. Presence enables it (explicit enabled=false disables it in templated configs). The widget is injected into matching HTML pages and every matching request is verified against the Siteverify API BEFORE the backend: a missing/invalid token is answered 403 (readable page for a navigation, JSON otherwise), a Siteverify outage 503 (fail closed). The block is global on purpose: one place to change, no per-host duplication. LIMITATION: it protects only traffic that passes through this Gate One instance; a second ingress, an internal caller or a direct port reaching the application bypasses it. Network isolation (only Gate One can reach the application) remains required. The token is never logged nor written to usage events. | ||
no | Out-of-process usage-event bus (WAL + extension plugins). Presence enables it. Events are produced only for the subscribed types; a full durable share is answered with a targeted 503 (health/readyz/auth-probe exempt). | ||
array of object | yes | Routes served by this gateway. Order is not significant: the most specific host/path match wins. | |
array of object | no | Cross-domain SSO groups. Shared OIDC/client/session settings are declared once per group; providers[] declares only what varies per application domain. Expanded into authProviders at load. Auth-registry is exhaustive: a shared field placed in a provider entry is rejected by the strict decoder. |
securityHeaders
Platform-wide response hardening, emitted by the gateway on EVERY response it serves (proxied or generated: health, stats, 401/403, MCP metadata, PWA assets, CSP collector). Declaring the block applies the secure defaults; each field can be tightened or disabled. Referrer-Policy is only set when the response does not already carry one (a proxified app’s stricter policy wins). Absent = nothing (an install whose edge proxy already sets them can opt out).
| Field | Type | Required | Description |
|---|---|---|---|
integer | no | Strict-Transport-Security max-age, in seconds. Absent = 15552000 (180 days); 0 = header omitted (e.g. a host that must stay reachable in plain HTTP). Keep includeSubDomains/preload off until every subdomain is HTTPS-only: with preload the choice becomes permanent in browsers. Minimum: 0. Maximum: 63072000. | |
boolean | no | Append includeSubDomains. Default false. | |
boolean | no | Append preload. Requires hstsIncludeSubDomains (refused at load otherwise). Default false. | |
boolean | no | X-Content-Type-Options: nosniff. Default true. | |
string (one of: | no | Referrer-Policy value. Absent = strict-origin-when-cross-origin; empty string = header omitted. |
license
Optional offline signed licence (Ed25519), one token per period. Absent, invalid or expired = evaluation/personal mode: the gateway keeps serving; the user badge shows a notice. The private signing key NEVER ships (only publicKey is embedded).
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Signed licence token: v1.<base64url(JSON claims)>.<base64url(signature)>. | |
string | no | Deprecated single-token file. Prefer tokenDir (one token per period). Overridden by GATE_ONE_LICENSE_TOKEN; falls back to | |
string | no | Ed25519 public key, base64 (raw 32 bytes). Public by design, safe to commit. Legacy single-key form (tokens without kid). | |
map of string to string | no | kid → Ed25519 public key (base64). Several keys can be valid at once during a rotation; tokens carry their signed kid. An unknown kid is rejected. | |
string | no | Registration/pricing URL shown by the badge. | |
string | no | Directory holding one signed token per period: every regular file is read (convention licence-<start>-<end>.txt). The active periods are merged (nbf ⇐ now < exp): domains and features are unioned, domainCount and maxReplicas take the maximum. Dropping a file + SIGHUP is enough. | |
string (one of: | no | What to do when a configured host is not covered by the active licence: info (default, logged), warn (logged as a warning), strict (503 on that host). Eval (no active token) is never blocked. |
authProvider
Authentication provider. dev is the embedded loopback-only IdP used for onboarding/tests; http-bridge is any external service implementing the Gate One HTTP bridge contract (auth-bridge is the reference implementation; a non-conforming service needs an adapter); oidc is the native Relying Party (discovery, code+PKCE, JWKS, session, logout, MCP resource server); jwt is a stateless bearer verifier. Absent = legacy oidc + auth-bridge translation, so existing configs are unchanged. The legacy type http is accepted as a deprecated alias of http-bridge.
| Field | Type | Required | Description |
|---|---|---|---|
string (one of: | no | Provider implementation. Default: dev when a dev block is present, oidc when clientId is present, jwt when jwksUrl is present, http-bridge otherwise. | |
string | no | Unique provider id when declared in authProviders. The legacy authProvider block is normalized to id "default". | |
string | no | Session cookie name for type oidc. Empty = gate_one_session for the default provider, a provider-scoped name for additional providers. | |
string | no | Service root for type http-bridge (default http://auth-bridge:3000, or AUTH_BRIDGE_URL). | |
string | no | Public origin of the login service. For dev it hosts the embedded login page, e.g. http://login.demo.localhost:3000 (no path/query). | |
string | no | Full login page URL. Defaults to loginBaseUrl + /login, then (type http-bridge) the legacy oidc.loginUrl, then baseUrl + /login. | |
string | no | OIDC issuer advertised in MCP protected-resource metadata. Defaults to the legacy oidc.issuer, or loginBaseUrl + /dev for dev. | |
string | no | Shared parent domain of the session cookie (dev), e.g. demo.localhost. Required for dev. | |
string | no | Application to send a bare visit to THIS provider’s login host root to. Overrides the global defaultAppUrl for this login host. Must be a route host bound to this provider or a host covered by its cookieDomain. Prefer a neutral page rather than one application. | |
string | no | Identity verification endpoint (type http-bridge). Default baseUrl + /auth. | |
string | no | MCP bearer verification endpoint (type http-bridge). Default baseUrl + /auth/mcp. | |
string | no | Readiness endpoint (type http-bridge). Default baseUrl + /health. | |
no | Maps the identity response headers of an external provider (type http-bridge). Empty = auth-bridge defaults. | ||
string | no | OIDC client ID (type oidc). The client must be registered at the IdP with the redirect URI below. | |
string | no | Environment variable holding the OIDC client secret (type oidc). Absent/empty = public client (PKCE only). Never put the secret in this file. | |
array of string | no | OIDC scopes requested at the authorization endpoint. Default: openid, profile, email. | |
string | no | OIDC callback URL registered at the IdP. Default: loginBaseUrl + /auth/callback. | |
array of string | no | Dot paths in the ID token whose values become roles (default: realm_access.roles, resource_access.<clientId>.roles, groups). | |
string | no | Claim holding the granted scopes (default scope; scp is always also read). | |
string | no | MCP access-token audience policy. Empty = scope-only (compatible default). A literal value requires that value in aud. "auto" requires the endpoint’s own protected-resource identifier (recommended, and used with Keycloak audience mappers). | |
string | no | JWKS endpoint for type jwt. Required for jwt; the provider never hosts a login page. | |
string | no | Expected audience for type jwt (empty = no audience check, or the MCP resource when VerifyMCP is used). | |
array of string | no | Accepted JWT signing algorithms. Default: the algorithms advertised by the JWKS/issuer. | |
string | no | Bearer header name for type jwt. Default Authorization. | |
integer | no | JWKS cache TTL for type jwt. Default 300. Minimum: 0. | |
integer | no | OIDC RP session lifetime. Default 3600 (1h): a short TTL bounds revocation when no shared store is used. The refresh/reauth flow renews it. Minimum: 1. | |
no | Links owned by the authentication connector, displayed in the user badge. account is the IdP’s own account console: NO OIDC standard, so the URL is explicit (Keycloak /realms/<realm>/account/, Entra myaccount.microsoft.com, Google myaccount.google.com, Okta /enduser/settings…). | ||
string | no | Environment variable holding the RP cookie signing key (base64/random string, hashed to 32 bytes). Empty = the key is generated and persisted in the bbolt store. Required for multi-instance (shared key). | |
no | RP-side revocation store (back-channel logout, RP logout). Default: bbolt at ./gate-one-state.db, relative to the config file. bbolt is single-writer: a second Gate One instance on the same file is refused. Redis is the shared, multi-instance backend and makes revocation visible on the next request on every instance. | ||
boolean | no | OIDC Back-Channel Logout. Absent = auto: enabled when the IdP discovery advertises backchannel_logout_supported. false disables it. Register <loginBaseUrl>/backchannel-logout at the IdP. | |
boolean | no | Migration bridge: also set auth-bridge’s access_token/refresh_token/id_token cookies on cookieDomain, so auth-bridge’s token-broker proxy routes (/connections, /connect, /oauth) resolve the same session when both share the login host. Default false. The native session cookie remains authoritative for Gate One. | |
string | no | Migration bridge: after a valid local back-channel revocation, POST the same logout_token to this auth-bridge /backchannel-logout URL so its persistent sid list stays in sync for /auth and /connections. Best effort. | |
integer | no | Retention of revocation entries. Default: the issuer’s advertised refresh-token TTL (cladone_refresh_token_ttl_seconds, e.g. the embedded IAM), else 30 days; never shorter than the session TTL. Must cover the refresh-token lifetime for a global logout. A revocation covers only sessions/tokens issued before it, so a later sign-in is accepted (a user is never locked out by their own logout). Minimum: 1. | |
no | Embedded development IdP. Loopback-only; never production. |
authProvider.headers
Maps the identity response headers of an external provider (type http-bridge). Empty = auth-bridge defaults.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Default X-User-Id. | |
string | no | Default X-Username. | |
string | no | Default X-Email. | |
string | no | Default X-Roles (comma-separated). | |
string | no | Default X-Scopes (comma-separated). | |
string | no | Default X-Claims (base64 JSON). |
authProvider.links
Links owned by the authentication connector, displayed in the user badge. account is the IdP’s own account console: NO OIDC standard, so the URL is explicit (Keycloak /realms/<realm>/account/, Entra myaccount.microsoft.com, Google myaccount.google.com, Okta /enduser/settings…).
| Field | Type | Required | Description |
|---|---|---|---|
no | A link rendered in the expanded user badge. URL: http(s) absolute or a local absolute path (/…); javascript:/data: are refused. target defaults to _blank; rel is forced to noopener noreferrer. |
authProvider.links.account
A link rendered in the expanded user badge. URL: http(s) absolute or a local absolute path (/…); javascript:/data: are refused. target defaults to _blank; rel is forced to noopener noreferrer.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Displayed text. Optional for the connector links (default Account/Services); required for userBadge.links. Maximum length: 64. | |
string | yes | http(s) absolute URL or local absolute path (/…). Maximum length: 2048. | |
string (one of: | no | Link target. Default _blank. |
authProvider.sessionStore
RP-side revocation store (back-channel logout, RP logout). Default: bbolt at ./gate-one-state.db, relative to the config file. bbolt is single-writer: a second Gate One instance on the same file is refused. Redis is the shared, multi-instance backend and makes revocation visible on the next request on every instance.
| Field | Type | Required | Description |
|---|---|---|---|
string (one of: | no | bolt (default, single instance) or redis (shared, multi-instance, immediate revocation). | |
string | no | bbolt file path. Default ./gate-one-state.db (relative to the config file). | |
string | no | Redis URL for type redis (redis:// or rediss://). Mutually exclusive with sentinel mode. | |
array of string | no | Redis Sentinel addresses (host:port). Required with masterName. | |
string | no | Redis Sentinel master name. Requires sentinelAddrs. | |
string | no | ACL user used to authenticate to the Sentinels themselves (sentinel mode only). Separate from username, which is the Redis data user. | |
string | no | Environment variable holding the Sentinel password (sentinel mode only). Never put the password in this file. | |
boolean | no | Use TLS in sentinel mode (Sentinels and the discovered master). In URL mode use a rediss:// URL instead. | |
string | no | PEM CA bundle for the Sentinel TLS connections. Requires tls=true; empty = system roots. | |
string | no | TLS server name override (useful when the Sentinels are addressed by IP). Requires tls=true. | |
string | no | Redis ACL username. | |
string | no | Environment variable holding the Redis password. Never put the password in this file. | |
integer | no | Redis logical database. Default 0. Minimum: 0. | |
string | no | Shared revocation key prefix. Default cladone:auth:v1:. Must match the embedded IAM’s redis.authPrefix. | |
integer | no | Redis dial timeout in milliseconds. Default 500. Minimum: 0. | |
integer | no | Redis read timeout in milliseconds. Default 300. Minimum: 0. | |
integer | no | Redis write timeout in milliseconds. Default 300. Minimum: 0. | |
integer | no | Redis connection pool size. Default 64. Minimum: 0. |
authProvider.dev
Embedded development IdP. Loopback-only; never production.
| Field | Type | Required | Description |
|---|---|---|---|
array of object | no | Development users exposed by the embedded login form. Loopback only. | |
integer | no | Session lifetime. Default 43200 (12h). Minimum: 1. |
authProvider.dev.users
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Login name for the development user. | |
string | yes | Plaintext, dev only. | |
array of string | no | Roles granted to this development user. | |
array of string | no | OAuth scopes granted to this development user. | |
map of string to any | no | Additional claims returned for this development user (string values). | |
string | no | Bearer accepted by MCP routes. Default dev-<username>. |
oidc
LEGACY platform IdP block. When authProvider is absent, it is translated into an http provider (auth-bridge) at load; existing configs keep working. New configurations should use authProvider.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Platform login page (e.g. "https://login.example.com/login"). Used by the server-side redirect of frontend routes and by the guard script (per-route authGuard.loginUrl takes precedence). | |
string | no | OIDC issuer (e.g. "https://auth.example.com/realms/example") — announced as the authorization_server of the MCP OAuth metadata. |
tokenBroker
Internal per-user credential broker. Required when a route declares upstream.tokenBroker; a configured block also keeps the Services page (/connections: pause/resume/erase) available when no route delegates. The shared secret is read from the named environment variable at load (never from this file) and is never logged.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Broker base URL (default http://token-broker:4000, the Docker internal service name). Never expose it through an ingress: the broker is an internal service. | |
string | no | Environment variable holding the X-Broker-Secret shared secret (default TOKEN_BROKER_SHARED_SECRET). | |
no | Links owned by the token broker, displayed in the user badge. services is the services/connections page; it is rendered whenever the broker is configured (the gateway computes the final list at load). | ||
string | no | Host on which Gate One serves the token broker’s browser routes (/connections, /connect/, /oauth//callback), authenticating the user and injecting the identity. Empty = derived from links.services.url (its hostname). If both are absent, Gate One does not serve them; an ingress must never proxy those paths directly to the broker (it would answer 401). auth-bridge does not receive the shared secret. |
tokenBroker.links
Links owned by the token broker, displayed in the user badge. services is the services/connections page; it is rendered whenever the broker is configured (the gateway computes the final list at load).
| Field | Type | Required | Description |
|---|---|---|---|
no | A link rendered in the expanded user badge. URL: http(s) absolute or a local absolute path (/…); javascript:/data: are refused. target defaults to _blank; rel is forced to noopener noreferrer. |
tokenBroker.links.services
A link rendered in the expanded user badge. URL: http(s) absolute or a local absolute path (/…); javascript:/data: are refused. target defaults to _blank; rel is forced to noopener noreferrer.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Displayed text. Optional for the connector links (default Account/Services); required for userBadge.links. Maximum length: 64. | |
string | yes | http(s) absolute URL or local absolute path (/…). Maximum length: 2048. | |
string (one of: | no | Link target. Default _blank. |
mcpDefaults
Optional defaults shared by every MCP access unit.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Default MCP scope when a service/set declares none. |
mcpSets
An MCP access unit grouping services under one endpoint; carries the audience (resource), scope and identity of the group.
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Public host serving the set. | |
string | yes | Mount path of the set on that host ("/" allowed). | |
string | no | OAuth protected-resource identifier (audience) of the set. Empty = the set endpoint URL. | |
string | no | MCP scope required from clients of this set. Default: mcpDefaults.scope, else mcp-tools. | |
no | Identity policy conditions, evaluated AFTER authentication on the verified token (failure = 403 forbidden). All conditions combine with AND. | ||
boolean | no | Expose the set (default true). false = the whole set is skipped (no endpoint, no validation). | |
array of object | yes | — |
mcpSets.identity
Identity policy conditions, evaluated AFTER authentication on the verified token (failure = 403 forbidden). All conditions combine with AND.
| Field | Type | Required | Description |
|---|---|---|---|
array of string | no | Required Keycloak roles (realm roles + flattened client roles, see auth-bridge’s X-Roles). AND between them. Example of a fixed name: offline_access (realm role granted to every user via default-roles-<realm>). | |
array of string | no | Required granted scopes (token | |
map of string to string or array of string | no | Token claims: key = claim name, value = one accepted string or a list of accepted strings (OR / anyOf on the same claim). "*" = the claim is present. All keys combine with AND. A token claim that is itself a list (groups, roles…) matches when one of its elements matches. Example: {"email": ["alice@example.com", "a.rolland@example.com"], "email_verified": "true"}. | |
string | no | Exact username required (preferred_username). |
mcpSets.members
One service mounted under a set.
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | mcp.name of the referenced service. | |
string | yes | Path relative to the set endpoint ("/" = the set endpoint itself). | |
array of string | no | Optional glob allow-list restricting the service’s tools inside this set. | |
boolean | no | Expose this member (default true). false = not materialized, and its service is neither compiled (no OpenAPI fetch) nor provider-validated. |
publisherFeed
Optional signed publisher feed: compatible Gate One versions, external components and plugins, with their revocation status. Loaded and verified at startup; the operator’s trust store stays authoritative. Absent = no feed is consulted.
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Feed path, relative to the config file. Minimum length: 1. | |
map of string to string | yes | Feed signing keys: key id → base64 Ed25519 public key. The feed’s signature key id must be present. Provisioned out-of-band. | |
string (one of: | no | warn (default): report revocations and incompatibilities only. enforce: apply the feed’s revocations (digest and key) to the plugin trust store, so a revoked plugin refuses to load. |
turnstile
Platform-wide Cloudflare Turnstile bot check for form pages. Presence enables it (explicit enabled=false disables it in templated configs). The widget is injected into matching HTML pages and every matching request is verified against the Siteverify API BEFORE the backend: a missing/invalid token is answered 403 (readable page for a navigation, JSON otherwise), a Siteverify outage 503 (fail closed). The block is global on purpose: one place to change, no per-host duplication. LIMITATION: it protects only traffic that passes through this Gate One instance; a second ingress, an internal caller or a direct port reaching the application bypasses it. Network isolation (only Gate One can reach the application) remains required. The token is never logged nor written to usage events.
| Field | Type | Required | Description |
|---|---|---|---|
boolean | no | Explicit switch for templated configurations. Absent = enabled when the block is present. | |
string (one of: | no | Protection mode. forms: inject the widget into forms and verify the configured methods. gate: challenge the first request to a protected path, set a signed clearance cookie (clearanceTtlSeconds) and let the following requests through without a new Siteverify call; a form POST without clearance is refused (the submitted data cannot be replayed), an API/fetch call gets 403 turnstile_required. both: gate AND form verification. Default forms. | |
string | yes | Public Turnstile site key (safe in the page). Cloudflare binds it to a domain list: every public hostname serving the form must be declared there. Minimum length: 1. Maximum length: 256. | |
string | yes | Name of the environment variable holding the Turnstile secret key. Required: the secret is only read from the environment, never from this file, and never sent to the browser. Missing/empty variable refuses startup. Minimum length: 1. | |
array of string | no | Hosts the block applies to: exact hostnames, or a leading . wildcard on a label boundary (.example.com). Empty = every route. | |
array of string | no | Paths the block applies to, with the route.path grammar: /contact/* (prefix on a path boundary), an exact path, or /* (everything). Empty = every path. The widget is injected only on matching pages; verification only on matching requests. | |
array of string (one of: | no | Request methods verified. Default POST. GET/HEAD are never challenged (the page must load before the widget can run). | |
array of string | no | CSS selectors of the forms that receive the widget. Empty = every POST form on the page. A form marked data-gtw-turnstile="off" is always skipped. An invalid selector is ignored (no injection, no error). | |
string | no | Optional action sent with the widget and required in the Siteverify answer (1-32 letters, digits, _ or -). Binds the token to this form: a token obtained for another action is refused. Pattern: | |
string | no | Optional expected hostname in the Siteverify answer. Empty = no check (the site key’s domain list already constrains it). Useful when one site key serves several hosts and each must be pinned. | |
string | no | Header accepted in addition to the cf-turnstile-response form field, for fetch/SPA clients. Default X-Turnstile-Response. The injected script attaches the token to same-origin mutating fetches automatically. Pattern: | |
array of string | no | Paths never gated in gate/both mode (machine endpoints, webhooks, MCP). The route.path grammar applies (/api/webhooks/, exact path). Built-in exemptions always apply: /healthz, /readyz, /__gate-one/, /favicon.ico, /robots.txt. OPTIONS requests are never gated. | |
integer | no | Lifetime of the gate clearance cookie. Default 1800 (30 minutes). A visitor who solved the challenge is not challenged again before it expires; rotating the signing key invalidates every clearance. Minimum: 60. Maximum: 604800. | |
string | no | Environment variable holding the HMAC key that signs the clearance cookie. Optional: when empty, the key is derived from the Turnstile secret with domain separation (rotating that secret then invalidates every clearance). A dedicated key lets you rotate clearances without touching the widget secret. Must be identical on every replica. | |
no | Challenge page tuning (gate/both). | ||
boolean | no | gate/both: the injected script solves the challenge in the background when a page is served without clearance, so the first user action needs a single backend call. Default true. The 403 turnstile_required + replay remains the fallback. |
turnstile.challenge
Challenge page tuning (gate/both).
| Field | Type | Required | Description |
|---|---|---|---|
string (one of: | no | Turnstile render appearance of the challenge widget. Default interaction-only in gate/both: the widget stays hidden unless Cloudflare requires an interaction. always shows it; execute renders without running the challenge until executed. |
usage
Out-of-process usage-event bus (WAL + extension plugins). Presence enables it. Events are produced only for the subscribed types; a full durable share is answered with a targeted 503 (health/readyz/auth-probe exempt).
| Field | Type | Required | Description |
|---|---|---|---|
string | no | WAL directory (one subdirectory per event type). Relative paths are resolved from the config file. Default ./gate-one-usage. | |
string | no | Instance identifier written in every event. Default: hostname. | |
string | no | Environment variable holding the HMAC key deriving the opaque subjectId. Required. | |
string | no | Environment variable holding the HMAC key signing host→plugin assertions. Required. | |
integer | no | Node WAL budget in bytes, split across the type shares. Default 52428800 (50 MiB). Minimum: 1. | |
string | no | Path of the signed plugin trust store (JSON: keys, allowedNames, allowedDependencies, revokedDigests, allowAuth, coreVersion). Required when a plugin declares a signed manifest. Relative paths are resolved from the config file. | |
boolean | no | Refuse any plugin without a signed manifest. Default false: an unsigned plugin is allowed for development and logged as a warning. | |
string | no | Directory scanned for plugin descriptors: every *.json file is a plugin entry (same shape as usage.plugins[]). Manifest and artifact paths are relative to the descriptor. Merged with usage.plugins; duplicate names are refused. Rescanned on SIGHUP. | |
map of object | no | Durable event type → WAL policy. A plugin can only subscribe to a configured type. | |
array of object | no | Opt-in capture of allow-listed query parameters and JSON body fields into matching events (default: billing). Scoped by host/route/method; values are capped and sensitive names skipped. | |
string (one of: | no | Spending authorisation profile: online (a lease refilled from the managed ledger) or airgap (a signed allowance file, valid for a period). Default online. The airgap profile bypasses the managed settlement split and therefore requires the "billing-byo" licence feature; the gateway refuses to start without it. | |
map of object | no | Billable metrics, keyed by metric code. A route references one with routes[].metric. Empty = the metering path is disabled. | |
map of object | no | Versioned rate tables, keyed by version. Each entry names its metric and effective date. | |
no | Bounded local delivery buffer for billing records. It survives a transport outage for a few hours, then refuses new billable requests; it is not a financial archive. | ||
no | Delivery of billing records: signed push to the managed endpoint, or a signed export bundle when the endpoint is unreachable (airgap). | ||
string | no | Signed allowance file (airgap profile). Resolved relative to the configuration file. | |
string | no | Environment variable holding the signed allowance JSON. Mutually exclusive with allowanceFile. | |
string | no | Base64 Ed25519 public key verifying the allowance (airgap profile). | |
array of object | no | Out-of-process event consumers (HTTP/JSON), each with its own subscription or signed manifest. |
usage.types
| Field | Type | Required | Description |
|---|---|---|---|
integer | yes | Maximum size of this type’s WAL on this node. Minimum: 1. | |
string (one of: | no | Durability: none, group commit every windowMs, or fsync before the response (zero_loss). always is refused for request/error (the proxy emitter cannot fsync before a streaming response). | |
integer | no | Group-commit interval for fsync=window_ms. Default 100. Minimum: 1. | |
integer | no | Append-file rotation size. 0 = share/4 capped at 8 MiB. Minimum: 0. |
usage.capture
| Field | Type | Required | Description |
|---|---|---|---|
array of string | no | Request hosts or glob patterns to capture. Empty = all hosts. | |
array of string | no | Request path patterns to capture. Empty = all paths. | |
array of string | no | HTTP methods to capture. Empty = all methods. | |
array of string | no | Event types to attach attrs to. Default: [billing]. | |
array of string | no | Allow-listed query parameter names whose values are copied as q.<name>. Sensitive names are skipped. | |
no | Optional JSON-body extraction. | ||
integer | no | Total attrs budget in bytes. Default 1024, clamped to 2048. Minimum: 0. Maximum: 2048. |
usage.capture.body
Optional JSON-body extraction.
| Field | Type | Required | Description |
|---|---|---|---|
string (one of: | no | Body parser; only json is supported. | |
array of string | no | Dotted JSON paths ($.a.b or a.b) whose scalar values are copied as b.<path>. | |
integer | no | Skip body capture when the raw body is larger. 0 = no limit. Minimum: 0. |
usage.metrics
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Free-form unit label (request, token, byte, second, …). Minimum length: 1. | |
integer (one of: | yes | Decimal scale of the quantity: 0 (units), 3 (milli) or 6 (micro). | |
integer | no | Maximum number of dimensions accepted per measure; must not exceed the declared dimensions. Minimum: 0. | |
map of object | no | Declared dimension names and their types; an undeclared dimension is rejected. | |
string (one of: | no | event (discrete fact) or periodic (continuous quantity emitted by heartbeat, e.g. time, bytes, storage). Default event. | |
integer | no | Heartbeat period of a periodic metric. Default 60. Minimum: 10. Maximum: 3600. | |
string (one of: | no | declarative (core rate table) or plugin (signed pricing plugin). Default declarative. | |
string | no | Declarative tariff version used when pricing=plugin is unavailable. Required with pricing=plugin. |
usage.tariffs
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Minimum length: 1. | |
string | yes | Minimum length: 1. | |
string | yes | Minimum length: 3. Maximum length: 3. | |
string | yes | — | |
array of object | yes | — |
usage.tariffs.charges
| Field | Type | Required | Description |
|---|---|---|---|
string (one of: | yes | — | |
integer | no | Minimum: 0. | |
map of string to array of string | no | — | |
array of object | no | — | |
integer | no | Minimum: 0. | |
integer | no | Minimum: 0. | |
integer | no | Minimum: 0. | |
integer | no | Minimum: 0. |
usage.outbox
Bounded local delivery buffer for billing records. It survives a transport outage for a few hours, then refuses new billable requests; it is not a financial archive.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Outbox directory. Default ./usage-outbox, resolved from the configuration file. | |
string | no | Maximum age of the oldest pending record (duration, e.g. 6h). Default 6h. | |
integer | no | Maximum pending bytes. Default 512 MiB. Minimum: 0. | |
integer | no | Maximum pending records. Default 50000. Minimum: 0. | |
string (one of: | no | block (default): new billable requests answer 503 when the buffer is full. drop: discard (counted, logged) — never silent but loses revenue. |
usage.sink
Delivery of billing records: signed push to the managed endpoint, or a signed export bundle when the endpoint is unreachable (airgap).
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Managed ingest endpoint (http/https). Empty = export only. | |
string | no | Environment variable holding the Bearer secret sent with every push. | |
string | no | Environment variable holding the base64url 32-byte Ed25519 seed that signs each batch and each export bundle. Required when metrics are configured. | |
integer | no | Maximum records per batch. Default 500. Minimum: 1. | |
integer | no | Maximum batch size in bytes. Default 1 MiB. Minimum: 1. | |
integer | no | Maximum wait before flushing a partial batch. Default 1000 ms. Minimum: 1. | |
string | no | Directory receiving signed export bundles (bundle .jsonl, .sig and manifest). Required for the airgap profile. | |
integer | no | How long the push may fail before an export bundle is written. Default 300 s. Minimum: 0. | |
string | no | Base64 Ed25519 public key of the ingest service, verifies the signed receipts that acknowledge an air-gap bundle (gate-one billing ack). Required for the airgap profile. |
usage.plugins
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Unique plugin name (assertion audience). Minimum length: 1. | |
string | yes | Absolute http(s) base URL. /health and /events are appended. | |
string | no | Path of the signed plugin manifest. When set, the manifest is authoritative for the event subscription and the licence feature; artifact is required and usage.trustStore must be configured. | |
string | no | Path of the signed plugin artefact (binary/container). Required with manifest; its digest is verified against the manifest. | |
array of string (one of: | no | Subscribed event types when no signed manifest is used; each must be configured in usage.types. | |
no | Narrow the subscription by host, route, tenant, method, status class or sample rate. Route patterns match the path only, so use hosts to avoid matching the same path on every host. | ||
integer | no | Allowed subscriber lag. Beyond it, onMaxLag applies. Minimum: 0. | |
string (one of: | no | block (default): 503 until catch-up; drop: move the cursor to the lag limit and let eviction proceed. | |
string | no | Required signed-licence feature; the plugin is refused without it. |
usage.plugins.filters
Narrow the subscription by host, route, tenant, method, status class or sample rate. Route patterns match the path only, so use hosts to avoid matching the same path on every host.
| Field | Type | Required | Description |
|---|---|---|---|
array of string | no | Request hosts or glob patterns to include (e.g. app.example.com, *.example.com). Empty = all hosts. | |
array of string | no | Route patterns to include, for example /api/*. Empty = all routes. | |
array of string | no | Tenant or auth-provider ids to include. Empty = all tenants. | |
array of string | no | HTTP methods to include. Empty = all methods. | |
array of string | no | HTTP status classes to include (1xx..5xx). Empty = all classes. | |
number | no | Sampling rate for matching events, 0..1. Default 1.0. Minimum: 0. Maximum: 1. |
routes
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Single literal application host (or wildcard pattern when wildcard is set). Mutually exclusive with hosts. | |
array of string | no | Literal application hosts sharing this route body. Expanded at load into one concrete route per host; auth.provider is inferred from the ssoGroups provider whose cookieDomain covers each host. Mutually exclusive with host and wildcard. | |
map of string to array of string | no | Named host/backend expansion — one route, N sites. Keys = capture names ({name} in host and backend), values = the ALLOW-LISTS of DNS labels they may take (a-z0-9, hyphens, no dots). The route expands at load into one concrete route per label; a label outside the lists has no route (fail-closed: the backend is never attacker-derived). With rewriteBody enabled, the expansion also compiles the cross-route map: an origin of another variant (e.g. es.wikipedia.org in the English edition) is rewritten to that sibling’s public host; ambiguous alias mappings are left alone, and route-local except prefixes do not apply to siblings. | |
string | yes | Prefix; "/*" = wildcard (whole host). | |
string | no | Exact PUBLIC path prefix removed from the request before it reaches the backend. Path only (no query, fragment or wildcard), matched on segment boundaries and normalized at load (trailing slash removed; root "/" forbidden). Example: path "/api/*", stripPrefix "/api", backend "http://api:8080/v1" turns /api/foo into /v1/foo. The backend also receives X-Forwarded-Prefix when the ingress did not set one. Response paths are rewritten symmetrically: Location, Content-Location, Refresh/Link headers, Set-Cookie Path, and HTML URL attributes (href/src/action/srcset/poster, style url(), meta refresh) are brought back under /api; relative references and other origins are left untouched. Script strings and CSS @import are out of scope. Maximum length: 256. | |
string | yes | Backend base URL (http/https) that serves the route. | |
string | no | Path of the route’s custom injection bundle (JSON), relative to the config file. The bundle declares HTML/JS/CSS insertions by named target ("head" = after <head>/meta charset, "body-end" = before </body>) with per-page targeting (when = path prefix) and data-* attribute patches applied client-side by the guard. Inline content or file: (relative to the bundle, read at load — a faulty bundle refuses startup). Its formal grammar is inject-bundle.schema.json. See the doc’s § upstream.inject. | |
no | Join to a backend outside the internal network (proxified external site — e.g. Cloudflare Pages/Workers). Absent = unchanged historical behavior: public Host preserved, client headers passed as-is. | ||
boolean | no | Page route: auth failure → 302 to the login (API routes get a JSON 401); eligible for the guard script and the pwa policy. | |
boolean | no | Emit usage events for this route. Default true; set false to serve the route without producing ANY usage event — no WAL reservation and no admission 503 on this route. Use it for control or monitoring routes (a status page, an events viewer) that must not pollute, or depend on, the event bus. | |
string | no | Billable metric produced by this route (a key of usage.metrics). The amount is rated and reserved BEFORE the backend call; an unaffordable request answers 402 instead of running. Empty = the route does not use the metering path. | |
boolean | no | HEAD requests are served as GET, body removed. | |
no | Authentication policy. Empty ({}) = authentication ALWAYS required. auth.when holds TECHNICAL conditions (method/query/header/body) deciding when authentication is required: one object (AND of its conditions) or an array (OR of ANDs — required if ANY element matches); absent/empty = always required. auth.identity holds IDENTITY conditions (roles/scopes/claims/user), checked after successful authentication (403 otherwise) and only on requests that were actually authenticated. | ||
no | Rate limiting: fixed window per identity (authenticated user, else real IP via X-Forwarded-For), per route. Full window = 429 + Retry-After (seconds). Periods: second|minute|hour|day. | ||
no | Cumulative quota: same mechanics as rateLimit with a longer window in practice (day). Full window = 429 + Retry-After. | ||
no | OpenAPI document used to derive REST operations and MCP tools for this route. | ||
no | Expose a route as an MCP service: protected-resource identifier, scope, tools and metadata. | ||
no | PWA shell, manifest, icons and service-worker cache policy for a frontend route. | ||
no | Content-Security-Policy emitted (or amended) by the gateway — the only component able to generate a per-response nonce and write it into the HTML it already rewrites (guard, PWA, custom injections). By default the origin’s policy is NEVER removed or weakened: the gateway’s policy is added as a SECOND header (browsers enforce all of them, so the effective rule is the strictest) and the hashes of the gateway’s own injected script and guard stylesheet are APPENDED to the origin’s script-src / style-src (script-src-elem > script-src > default-src, style-src-elem > style-src > default-src). Set own=true to remove the origin policy explicitly and let the gateway own it (opt-in, logged, requires mode nonce/hash and reportOnly=false). Applied to HTML responses only. | ||
no | User badge injected by the guard script once authenticated: collapsed on the top edge (one line = username), expanded on hover/keyboard focus — chosen claims + logout button (OIDC end_session + cookie clearing). Absent = no badge. | ||
array of object | no | UI gating by config (CSS selectors) — embedded in the injected guard script. Only for non-modifiable fronts; an element’s data-gtw-* markup wins over the rules. | |
no | 401 guard script (login without reloading: tab + replay + banner + declarative gating). Active by default on frontend routes; no-op elsewhere. The guard intercepts the 401 (login) and, when authGuard.forbidden.banner is set, the gateway’s OWN identity 403; a 403 coming from the backend is never concerned. |
routes.upstream
Join to a backend outside the internal network (proxified external site — e.g. Cloudflare Pages/Workers). Absent = unchanged historical behavior: public Host preserved, client headers passed as-is.
| Field | Type | Required | Description |
|---|---|---|---|
boolean | no | true = the Host header sent to the backend is that of the backend’s origin (required for an external site that routes by Host, e.g. Cloudflare — otherwise the request arrives with the public host and the origin does not recognize it). Absent/false = public Host passed as-is (internal backends). TLS/SNI always targets the backend’s origin. | |
string | no | Explicit value of the Host header sent to the backend, when it must differ from both the public host and the backend’s origin (rare). Precedence over host. | |
map of string to string | no | Static request headers set toward the backend (Set: overwrite any value the client may have sent). Uses: get through an origin basic auth ("Authorization": "Basic ${MYSITE_BASIC_AUTH}") or the Cloudflare Access service tokens (CF-Access-Client-Id / CF-Access-Client-Secret). Values with ${VAR} interpolation from the container environment — missing variable = fatal error at load (never a silent empty credential); empty value = header removed from the request. Values are never logged. | |
boolean | no | true = ABSOLUTE Locations targeting the backend’s origin are rewritten to the public host (https scheme, path/query preserved) — otherwise the browser would leave the proxy at the target site’s first absolute redirect (e.g. 301 of the trailing slash). Relative Locations and those targeting other origins (e.g. a Cloudflare Access team domain) pass intact. Default: false. | |
no | Adjustment of the backend’s RESPONSE headers before sending to the browser — typically remove an origin Content-Security-Policy that would block the injected guard script, or set a deployment header. Prefer csp.merge (hashes) or csp.own (explicit removal, logged) for CSP: removing the origin’s CSP here is a blunt, unlogged operation and loses the origin’s protections. | ||
no | Rewriting of the HTML body (200 text/html only, STREAMING — O(1) memory): the origin’s absolute URLs cited in the pages ("https://origin/…", "http://origin/…", "//origin/…") are rewritten to the route’s public host, so the browser stays in the proxy. Any explicit port is part of the rewritten prefix and is lost (target served on the standard port). The path, query and fragment follow as-is. Routes expanded from one wildcard automatically rewrite sibling editions toward their public hosts. | ||
no | Translation of the Set-Cookie set by the origin: a Domain attribute targeting its own namespace ("Domain=original…domain.dev") would be rejected by the browser from the public host — the cookie would be silently lost. Active: a Domain targeting the origin host (equal or parent) is rewritten to the public host in "rewrite" mode (the sharing scope — host + subdomains — is transposed as-is), or the attribute is removed in "strip" mode (host-only on the public host). Other cookies pass intact (surgery of the Domain attribute only: Path/Secure/HttpOnly/SameSite/Expires come out byte-exact). Auth protection: an origin Set-Cookie bearing a name reserved for the gateway session (access_token, refresh_token, id_token, session) is removed with a warning — set on the same public host, it would overwrite the session cookies. Default: absent (no cookie rewriting). | ||
string (one of: | no | REQUEST-side cookie filtering toward the backend. The gateway session cookies (access_token, refresh_token, id_token, session) carry Domain=<parent domain> (COOKIE_DOMAIN, e.g. .cladone.com): the browser presents them to EVERY public host, so without this filter a proxified third-party site receives the user’s tokens on every request. "all" = every cookie removed except the names in keep (the proxified app’s own cookies); "none" = historical behavior, everything forwarded. Default: "all" as soon as an upstream policy is declared (fail-closed). | |
array of string | no | Cookie names preserved when stripCookies is "all" (typically the proxified application’s own session). Names reserved for the gateway session (access_token, refresh_token, id_token, session) are refused at load: they must never reach an external origin. | |
no | HTTP-cache orientation to the BROWSER for assets the origin serves with no Cache-Control (and no Last-Modified either: without these two headers the browser has no heuristic cache — every re-view re-fetches or revalidates each asset, one full round-trip each). Only 200 responses of content-type image/*, Cache-Control absent only: the origin’s own declarations pass untouched. Sent as "private, max-age=…" — private: resources under authentication stay out of any shared cache; the origin’s ETag remains for revalidation after expiry. Default: absent (headers preserved as-is). | ||
no | Per-user credentials supplied by the token broker for the authenticated caller. Requires the top-level tokenBroker block and route.auth (the broker needs an authenticated user id). A missing connection produces a 401/403/409 with a connect_url (403 when the user paused the delegation, 409 when reauthorization is required), or a redirect for frontend routes, never a request without credentials. The same policy applies to REST tool calls produced by an MCP route. |
routes.upstream.responseHeaders
Adjustment of the backend’s RESPONSE headers before sending to the browser — typically remove an origin Content-Security-Policy that would block the injected guard script, or set a deployment header. Prefer csp.merge (hashes) or csp.own (explicit removal, logged) for CSP: removing the origin’s CSP here is a blunt, unlogged operation and loses the origin’s protections.
| Field | Type | Required | Description |
|---|---|---|---|
map of string to string | no | Headers added/overwritten on the response; values with ${VAR} interpolation (container environment); empty value = header removed (never transmitted empty). | |
array of string | no | Header names removed from the response (e.g. Content-Security-Policy). |
routes.upstream.rewriteBody
Rewriting of the HTML body (200 text/html only, STREAMING — O(1) memory): the origin’s absolute URLs cited in the pages ("https://origin/…", "http://origin/…", "//origin/…") are rewritten to the route’s public host, so the browser stays in the proxy. Any explicit port is part of the rewritten prefix and is lost (target served on the standard port). The path, query and fragment follow as-is. Routes expanded from one wildcard automatically rewrite sibling editions toward their public hosts. targets and followRoutes add explicit cross-route mappings (bare hosts only: the request’s scheme and port are inherited). At runtime (fetch/XHR/sendBeacon launched by the site), the client SW served on /__gate-one_pwa/sw.js applies the same policy — it is auto-registered by the guard script, served pre-authentication. One same policy (origins × exceptions × targets) is implemented three times (Go engine, client SW, script patch) — parity tested. Default: absent (no body rewriting).
| Field | Type | Required | Description |
|---|---|---|---|
boolean | no | true = engine active (the block is present to stay declarative; absent = disabled). | |
array of string or object | no | Exceptions by path prefix: string entry ("/reading/…") = these URLs stay at the ORIGIN (out-of-proxy reading link); object entry {path, to} = rewritten to an ALTERNATIVE public base. Longest prefix wins. '/…' prefix mandatory, '?'/'#' forbidden, 256 bytes max; 'to' = absolute http(s) URL, trailing slashes trimmed. | |
map of string to string | no | Explicit cross-route mappings: backend origin host → public route host. Bare hosts only (no scheme, port, path or wildcard); the key must be an origin of a declared route and the value the public host of a declared route. The request’s scheme and port are inherited. Automatic wildcard siblings are added before these entries; a conflicting mapping is refused at load. | |
array of string | no | Selectors over declared routes BY PUBLIC HOST (exact host or '.suffix'; a bare '' is refused). Each selected route’s backend origins are rewritten to its public host. Resolved after wildcard expansion; an unmatched selector, a self-only match or an ambiguous origin is refused at load. |
routes.upstream.rewriteCookies
Translation of the Set-Cookie set by the origin: a Domain attribute targeting its own namespace ("Domain=original…domain.dev") would be rejected by the browser from the public host — the cookie would be silently lost. Active: a Domain targeting the origin host (equal or parent) is rewritten to the public host in "rewrite" mode (the sharing scope — host + subdomains — is transposed as-is), or the attribute is removed in "strip" mode (host-only on the public host). Other cookies pass intact (surgery of the Domain attribute only: Path/Secure/HttpOnly/SameSite/Expires come out byte-exact). Auth protection: an origin Set-Cookie bearing a name reserved for the gateway session (access_token, refresh_token, id_token, session) is removed with a warning — set on the same public host, it would overwrite the session cookies. Default: absent (no cookie rewriting).
| Field | Type | Required | Description |
|---|---|---|---|
string (one of: | yes | "rewrite" (recommended) = Domain transposed to the public host; "strip" = Domain attribute removed. |
routes.upstream.browserCache
HTTP-cache orientation to the BROWSER for assets the origin serves with no Cache-Control (and no Last-Modified either: without these two headers the browser has no heuristic cache — every re-view re-fetches or revalidates each asset, one full round-trip each). Only 200 responses of content-type image/*, Cache-Control absent only: the origin’s own declarations pass untouched. Sent as "private, max-age=…" — private: resources under authentication stay out of any shared cache; the origin’s ETag remains for revalidation after expiry. Default: absent (headers preserved as-is).
| Field | Type | Required | Description |
|---|---|---|---|
integer | yes | Freshness duration (seconds) granted to the images (content-type image/*). E.g. 604800 = a week; 31536000 = a year (only if these files are immutable: the image names followed here are the capture timestamps, not content hashes — prefer a moderate duration if the app can re-edite a photo). Minimum: 1. |
routes.upstream.tokenBroker
Per-user credentials supplied by the token broker for the authenticated caller. Requires the top-level tokenBroker block and route.auth (the broker needs an authenticated user id). A missing connection produces a 401/403/409 with a connect_url (403 when the user paused the delegation, 409 when reauthorization is required), or a redirect for frontend routes, never a request without credentials. The same policy applies to REST tool calls produced by an MCP route.
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Broker provider name (e.g. github). Validated against GET /providers at startup; Gate One never stores the provider’s client secret. Pattern: | |
string | no | Backend request header receiving the access token. Default Authorization. Host, Cookie, X-User-, X-Forwarded- and X-Gate-One-* names are refused. | |
string | no | Value prepended to the access token. Default "Bearer ". An explicit empty string sends the raw token. |
routes.auth
Authentication policy. Empty ({}) = authentication ALWAYS required. auth.when holds TECHNICAL conditions (method/query/header/body) deciding when authentication is required: one object (AND of its conditions) or an array (OR of ANDs — required if ANY element matches); absent/empty = always required. auth.identity holds IDENTITY conditions (roles/scopes/claims/user), checked after successful authentication (403 otherwise) and only on requests that were actually authenticated.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | ID of the authProviders entry used to verify this route. Empty = defaultAuthProvider. The binding is static and compiled at load; a request cannot choose another provider. | |
object or array of object | no | Technical conditions deciding WHEN authentication is required. Empty = always. One object = AND; an array = OR of ANDs. | |
no | Identity policy conditions, evaluated AFTER authentication on the verified token (failure = 403 forbidden). All conditions combine with AND. |
routes.auth.identity
Identity policy conditions, evaluated AFTER authentication on the verified token (failure = 403 forbidden). All conditions combine with AND.
| Field | Type | Required | Description |
|---|---|---|---|
array of string | no | Required Keycloak roles (realm roles + flattened client roles, see auth-bridge’s X-Roles). AND between them. Example of a fixed name: offline_access (realm role granted to every user via default-roles-<realm>). | |
array of string | no | Required granted scopes (token | |
map of string to string or array of string | no | Token claims: key = claim name, value = one accepted string or a list of accepted strings (OR / anyOf on the same claim). "*" = the claim is present. All keys combine with AND. A token claim that is itself a list (groups, roles…) matches when one of its elements matches. Example: {"email": ["alice@example.com", "a.rolland@example.com"], "email_verified": "true"}. | |
string | no | Exact username required (preferred_username). |
routes.rateLimit
Rate limiting: fixed window per identity (authenticated user, else real IP via X-Forwarded-For), per route. Full window = 429 + Retry-After (seconds). Periods: second|minute|hour|day.
| Field | Type | Required | Description |
|---|---|---|---|
integer | yes | Maximum number of requests in the period. | |
string | yes | Rate-limit period: second, minute, hour or day. | |
no | Rate-limit / quota triggering conditions (rateLimit.when, quota.when). Two families: technical (method/query/header/body — evaluated BEFORE authentication) and identity (roles/scopes/claims/user — evaluated AFTER authentication on the verified token). All conditions combine with AND. NB: the authentication policy (auth) does NOT use this type: auth.when takes technical conditions only, auth.identity takes identity conditions only. |
routes.rateLimit.when
Rate-limit / quota triggering conditions (rateLimit.when, quota.when). Two families: technical (method/query/header/body — evaluated BEFORE authentication) and identity (roles/scopes/claims/user — evaluated AFTER authentication on the verified token). All conditions combine with AND. NB: the authentication policy (auth) does NOT use this type: auth.when takes technical conditions only, auth.identity takes identity conditions only.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Exact HTTP method (e.g. "POST"). | |
map of string to string | no | Exact equality of URL parameters. | |
map of string to string | no | Exact equality of headers. Caution: a header is forgeable by the client — never put an authorization there (use roles instead). | |
map of string to any | no | Exact equality of fields of the parsed JSON body (flattened one level). | |
array of string | no | Required Keycloak roles (realm roles + flattened client roles, see auth-bridge’s X-Roles). AND between them. Example of a fixed name: offline_access (realm role granted to every user via default-roles-<realm>). | |
array of string | no | Required granted scopes (token | |
map of string to string or array of string | no | Token claims: key = claim name, value = one accepted string or a list of accepted strings (OR / anyOf on the same claim). "*" = the claim is present. All keys combine with AND. A token claim that is itself a list (groups, roles…) matches when one of its elements matches. Example: {"email": ["alice@example.com", "a.rolland@example.com"], "email_verified": "true"}. | |
string | no | Exact username required (preferred_username). |
routes.quota
Cumulative quota: same mechanics as rateLimit with a longer window in practice (day). Full window = 429 + Retry-After.
| Field | Type | Required | Description |
|---|---|---|---|
integer | yes | Maximum billable units in the period. | |
string | yes | Quota period: second, minute, hour or day. | |
no | Rate-limit / quota triggering conditions (rateLimit.when, quota.when). Two families: technical (method/query/header/body — evaluated BEFORE authentication) and identity (roles/scopes/claims/user — evaluated AFTER authentication on the verified token). All conditions combine with AND. NB: the authentication policy (auth) does NOT use this type: auth.when takes technical conditions only, auth.identity takes identity conditions only. |
routes.quota.when
Rate-limit / quota triggering conditions (rateLimit.when, quota.when). Two families: technical (method/query/header/body — evaluated BEFORE authentication) and identity (roles/scopes/claims/user — evaluated AFTER authentication on the verified token). All conditions combine with AND. NB: the authentication policy (auth) does NOT use this type: auth.when takes technical conditions only, auth.identity takes identity conditions only.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Exact HTTP method (e.g. "POST"). | |
map of string to string | no | Exact equality of URL parameters. | |
map of string to string | no | Exact equality of headers. Caution: a header is forgeable by the client — never put an authorization there (use roles instead). | |
map of string to any | no | Exact equality of fields of the parsed JSON body (flattened one level). | |
array of string | no | Required Keycloak roles (realm roles + flattened client roles, see auth-bridge’s X-Roles). AND between them. Example of a fixed name: offline_access (realm role granted to every user via default-roles-<realm>). | |
array of string | no | Required granted scopes (token | |
map of string to string or array of string | no | Token claims: key = claim name, value = one accepted string or a list of accepted strings (OR / anyOf on the same claim). "*" = the claim is present. All keys combine with AND. A token claim that is itself a list (groups, roles…) matches when one of its elements matches. Example: {"email": ["alice@example.com", "a.rolland@example.com"], "email_verified": "true"}. | |
string | no | Exact username required (preferred_username). |
routes.openapi
OpenAPI document used to derive REST operations and MCP tools for this route.
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | OpenAPI 3.x JSON document of the host (absolute URL, or path relative to the route backend). Also the document used to resolve the mcp policy’s operations. | |
boolean | no | false = the openapi.json is only served authenticated. |
routes.mcp
Expose a route as an MCP service: protected-resource identifier, scope, tools and metadata.
| Field | Type | Required | Description |
|---|---|---|---|
boolean | yes | Enable the MCP service on this route. | |
string | no | Default: /mcp. | |
string | no | Service name, referenced by mcpSets members. Empty = the route cannot be a set member. | |
boolean | no | Expose this service on its own endpoint (default true). false = served only through the mcpSets referencing it (the set carries the access rights). | |
string | no | OAuth protected-resource identifier (audience) of the standalone service. Empty = the endpoint URL. Ignored when the service is reached through a set. | |
string | no | Keycloak client scope required from MCP clients (bearer | |
array of object | no | REST operations exposed as MCP tools. Empty = no tool (the MCP endpoint still serves metadata and initialization). Mutually exclusive with upstream. | |
no | Proxy an external MCP server over Streamable HTTP instead of deriving local REST tools. The upstream receives the per-user token of upstream.tokenBroker (or static upstream.headers), never the Gate One bearer. Tool filtering is enforced on both tools/list and tools/call. |
routes.mcp.operations
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Tool name exposed to MCP clients (unique per route). | |
string | yes | operationId resolved in the host’s OpenAPI document; the tool method, path, parameters and body schema are derived from it. An unresolved operationId refuses the configuration load. |
routes.mcp.upstream
Proxy an external MCP server over Streamable HTTP instead of deriving local REST tools. The upstream receives the per-user token of upstream.tokenBroker (or static upstream.headers), never the Gate One bearer. Tool filtering is enforced on both tools/list and tools/call.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Full upstream Streamable HTTP endpoint. Default: the route backend. The incoming /mcp path is not appended automatically. | |
no | Glob patterns (path.Match syntax) over MCP tool names. Include non-empty = only matching tools are exposed; Exclude always wins. Hidden tools are also refused on tools/call. |
routes.mcp.upstream.tools
Glob patterns (path.Match syntax) over MCP tool names. Include non-empty = only matching tools are exposed; Exclude always wins. Hidden tools are also refused on tools/call.
| Field | Type | Required | Description |
|---|---|---|---|
array of string | no | Allow-list patterns (e.g. "list_*", "get_*"). | |
array of string | no | Deny-list patterns (e.g. "delete_*", "*_secret"). |
routes.pwa
PWA shell, manifest, icons and service-worker cache policy for a frontend route.
| Field | Type | Required | Description |
|---|---|---|---|
boolean | yes | Enable the PWA shell on this route. | |
string | no | Application name shown in the PWA manifest. | |
string | no | Short application name for the home screen. | |
string | no | Theme color for the PWA manifest and browser UI. | |
string | no | Legacy singular (still read). | |
array of string | no | Same-host paths referenced by the manifest (Chrome: 192×192 and 512×512 PNG for full install). | |
no | One service-worker API cache policy (path prefix, method, retention). | ||
array of object | no | API cache policies for the service worker. | |
no | Its presence activates the universal GET cache (everything nothing else covers). | ||
no | Its presence activates the "never cached" route. When a PWA route shares the authentication scope, the identity-provider endpoints (discovery, authorize, token, JWKS, end-session) and the RP callback carrying a one-time code/state MUST be excluded, or the service worker can replay a stale identity or a one-time code. | ||
no | Strategy of same-origin pages/assets (documents, scripts, styles). Default: staleWhileRevalidate without timeout. |
routes.pwa.apiCache
One service-worker API cache policy (path prefix, method, retention).
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Path prefix of the API responses cached by the service worker. | |
string (one of: | no | POST (default) = network-first offline replay of POST/SSE; GET = SWR cache of this exact path; none = full opt-out (never cached, excluded from the apiGETCache catch-all). | |
integer | no | Default 50. | |
integer | no | Default 86400 (24 h). | |
integer | no | Default 2 MiB — beyond, streamed but not cached. | |
string | no | Semantic invalidation of the endpoint’s cache (bump it when the backend result changes). | |
integer | no | Default 1500 — finalizes the cached response after this SSE silence. | |
integer | no | Default 3 — network delay before the cache fallback (headers only). | |
boolean | no | Default true: offline, serves the cache directly. false = the network failure (bounded by networkTimeoutSeconds) triggers the fallback — for endpoints where replaying a write would be wrong. |
routes.pwa.apiCaches
One service-worker API cache policy (path prefix, method, retention).
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Path prefix of the API responses cached by the service worker. | |
string (one of: | no | POST (default) = network-first offline replay of POST/SSE; GET = SWR cache of this exact path; none = full opt-out (never cached, excluded from the apiGETCache catch-all). | |
integer | no | Default 50. | |
integer | no | Default 86400 (24 h). | |
integer | no | Default 2 MiB — beyond, streamed but not cached. | |
string | no | Semantic invalidation of the endpoint’s cache (bump it when the backend result changes). | |
integer | no | Default 1500 — finalizes the cached response after this SSE silence. | |
integer | no | Default 3 — network delay before the cache fallback (headers only). | |
boolean | no | Default true: offline, serves the cache directly. false = the network failure (bounded by networkTimeoutSeconds) triggers the fallback — for endpoints where replaying a write would be wrong. |
routes.pwa.apiGETCache
Its presence activates the universal GET cache (everything nothing else covers).
| Field | Type | Required | Description |
|---|---|---|---|
array of string | no | Path prefixes never cached by the catch-all. | |
array of string | no | JS regexes (full URL) never cached — invalid syntax rejected at load. |
routes.pwa.networkOnly
Its presence activates the "never cached" route. When a PWA route shares the authentication scope, the identity-provider endpoints (discovery, authorize, token, JWKS, end-session) and the RP callback carrying a one-time code/state MUST be excluded, or the service worker can replay a stale identity or a one-time code.
| Field | Type | Required | Description |
|---|---|---|---|
array of string | no | JS regexes (full URL) → straight to the network, no cache. |
routes.pwa.assets
Strategy of same-origin pages/assets (documents, scripts, styles). Default: staleWhileRevalidate without timeout.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | staleWhileRevalidate (default) | networkFirst | cacheFirst (case-free, normalized). | |
integer | no | For networkFirst (default 3). |
routes.csp
Content-Security-Policy emitted (or amended) by the gateway — the only component able to generate a per-response nonce and write it into the HTML it already rewrites (guard, PWA, custom injections). By default the origin’s policy is NEVER removed or weakened: the gateway’s policy is added as a SECOND header (browsers enforce all of them, so the effective rule is the strictest) and the hashes of the gateway’s own injected script and guard stylesheet are APPENDED to the origin’s script-src / style-src (script-src-elem > script-src > default-src, style-src-elem > style-src > default-src). Set own=true to remove the origin policy explicitly and let the gateway own it (opt-in, logged, requires mode nonce/hash and reportOnly=false). Applied to HTML responses only.
| Field | Type | Required | Description |
|---|---|---|---|
string (one of: | yes | "off" = nothing (the origin’s policy passes as-is); "merge" = keep the origin policy and only add the hashes of the gateway’s injected scripts (no policy of our own); "hash" = emit our policy with those hashes, no body rewrite; "nonce" = emit our policy with a per-response nonce and stamp the HTML in STREAMING mode (O(1) memory, upstream forced to gzip so the body is plain text). | |
boolean | no | Emit Content-Security-Policy-Report-Only instead of the enforcing header (progressive rollout: violations are collected, nothing is blocked). Requires mode nonce or hash. | |
boolean | no | The gateway OWNS the policy: the origin’s enforced and report-only CSP headers are removed before ours is emitted. Opt-in and logged (never silent). Requires mode nonce or hash and reportOnly=false: removing the origin’s enforcing policy to replace it with a report-only one would remove protection without enforcing anything. Use only for a site you control/accept, with a complete policy. | |
string | no | Local path served by the gateway collecting the violations (report-uri). Default: /__gate-one_csp/report. Bounded ring buffer (256 entries), 16 KiB body limit, 50 reports/second, never authenticated. Pattern: | |
string (one of: | no | "inline" (default) = only <script> tags WITHOUT src receive the nonce (external scripts stay governed by the host allow-list); "all" = every <script> tag. Nonce mode only. A tag that already carries a nonce is never rewritten. | |
map of string to string or array of string | no | Directive name → sources, as one string ("'self' https://x") or an array. The gateway appends the nonce (nonce mode) and the injected scripts' / guard stylesheet’s hashes to the effective script-src / style-src, and report-uri when reportTo is set. To let the guard’s nonce/hash pass, declare style-src (e.g. "'self'"). |
routes.userBadge
User badge injected by the guard script once authenticated: collapsed on the top edge (one line = username), expanded on hover/keyboard focus — chosen claims + logout button (OIDC end_session + cookie clearing). Absent = no badge.
| Field | Type | Required | Description |
|---|---|---|---|
string (one of: | no | Top edge (default right). | |
boolean or array of string | no | What the expanded badge displays as claims: true = every claim of the session, false = none, a list = only these (missing ones omitted). | |
boolean or array of string | no | Session roles displayed as chips in the expanded badge: true = every role, false = none, a list = only these (same data as the auth.identity.roles conditions, served by the /__gate-one/auth probe). | |
boolean or array of string | no | Granted scopes displayed as chips in the expanded badge: true = every scope, false = none, a list = only these. | |
string | no | Label of the logout button (default Log out). | |
array of object | no | Application-specific links, appended after the connector links (account, then services when the deployment delegates). Duplicate URLs are dropped (the connector link wins); at most 8 links after merge. | |
boolean | no | true = the badge ALSO appears when anonymous, from the very first visit, in invitation mode ("👤 Sign in") — clicked, it opens the login tab: offer sign-in without touching the site’s markup. Default false (badge only once authenticated; when anonymous it only appears after a detected session death). |
routes.userBadge.links
A link rendered in the expanded user badge. URL: http(s) absolute or a local absolute path (/…); javascript:/data: are refused. target defaults to _blank; rel is forced to noopener noreferrer.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Displayed text. Optional for the connector links (default Account/Services); required for userBadge.links. Maximum length: 64. | |
string | yes | http(s) absolute URL or local absolute path (/…). Maximum length: 2048. | |
string (one of: | no | Link target. Default _blank. |
routes.uiGate
UI gating rule by config — for fronts whose markup cannot be modified (if you control the HTML, prefer the data-gtw-* attributes). The selected element is visible only if the session satisfies the conditions (OR on roles, OR on scopes, AND on claims). PRECEDENCE: an element carrying at least one data-gtw-* attribute ignores uiGate rules; several rules on a same element combine with AND; invalid selector = rule ignored.
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | CSS selector (querySelectorAll) of the targeted elements. | |
array of string | no | Visible if AT LEAST ONE of these roles is held. | |
array of string | no | Visible if AT LEAST ONE of these scopes is granted. | |
map of string to string or array of string | no | All keys must match; each value is one accepted string or a list (OR / anyOf); "*" = presence. A token claim that is itself a list matches when one element matches. Example: {"email": ["alice@example.com", "a.rolland@example.com"]}. | |
string (one of: | no | Turns the selected elements into login triggers: after = native action preserved + invitation tab; before = click neutralized + direct tab. Activation only: deactivations (unchecking, closing details, reset, returning a value control to its OFF value) are never gated. Value controls (select/range/date/…) are gated on change, with their default value as the OFF value (data-gtw-auth-off on the element overrides it). | |
| string (one of: | no | Hiding: before = hidden before authentication (any click reaching it is neutralized → banner); after = hidden once authenticated. |
routes.authGuard
401 guard script (login without reloading: tab + replay + banner + declarative gating). Active by default on frontend routes; no-op elsewhere. The guard intercepts the 401 (login) and, when authGuard.forbidden.banner is set, the gateway’s OWN identity 403; a 403 coming from the backend is never concerned.
| Field | Type | Required | Description |
|---|---|---|---|
boolean | no | false = opt-out (the front handles its own 401). | |
string | no | Default: https://login.cladone.com/login (auth-bridge page). | |
no | Opt-in UX for the gateway’s OWN identity 403 (rights refusal — auth.identity not satisfied). A backend 403 is never concerned. Browser navigations get a readable HTML page (or a redirect to pageUrl); fetch()/XHR get the JSON 403 unchanged, plus an optional dismissable banner hinting the front authors to hide the feature. |
routes.authGuard.forbidden
Opt-in UX for the gateway’s OWN identity 403 (rights refusal — auth.identity not satisfied). A backend 403 is never concerned. Browser navigations get a readable HTML page (or a redirect to pageUrl); fetch()/XHR get the JSON 403 unchanged, plus an optional dismissable banner hinting the front authors to hide the feature.
| Field | Type | Required | Description |
|---|---|---|---|
boolean | no | true = the injected guard script shows a dismissable banner when a fetch() sees the gateway’s identity 403. Default false. | |
string | no | Browser navigations refused on identity are redirected there (302) instead of the built-in HTML page. | |
string | no | Overrides the built-in forbidden page’s message text. |
ssoGroups
One cross-domain SSO group. Every provider in the group shares issuer, client, scopes, session key and session store; only login host, cookie domain and domain-specific links may differ.
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Group identifier used in diagnostics and -check output. Minimum length: 1. | |
string (one of: | no | Provider type. Only oidc is allowed in a group; JWT providers stay standalone authProviders entries. Default: oidc. | |
string | yes | OIDC issuer shared by every provider in the group. | |
string | yes | OIDC client id shared by every provider. Register every provider callback URL at the IdP. Minimum length: 1. | |
string | no | Environment variable holding the shared OIDC client secret. Empty = public client (PKCE only). | |
array of string | no | OIDC scopes requested for every provider. | |
array of string | no | Dot paths in the ID token whose values become roles, shared by every provider. | |
string | no | Claim holding granted scopes. Default: scope; scp is always also read. | |
string | no | Environment variable holding the RP cookie signing key. Required with Redis; identical for every provider in the group. | |
no | RP-side revocation store (back-channel logout, RP logout). Default: bbolt at ./gate-one-state.db, relative to the config file. bbolt is single-writer: a second Gate One instance on the same file is refused. Redis is the shared, multi-instance backend and makes revocation visible on the next request on every instance. | ||
integer | no | OIDC RP session lifetime. Default 3600 (1h): a short TTL bounds revocation when no shared store is used. The refresh/reauth flow renews it. Minimum: 1. | |
boolean | no | OIDC Back-Channel Logout. Absent = auto: enabled when the IdP discovery advertises backchannel_logout_supported. false disables it. Register <loginBaseUrl>/backchannel-logout at the IdP. | |
string | no | Migration bridge: after a valid local back-channel revocation, POST the same logout_token to this auth-bridge /backchannel-logout URL so its persistent sid list stays in sync for /auth and /connections. Best effort. | |
integer | no | Retention of revocation entries. Default: the issuer’s advertised refresh-token TTL (cladone_refresh_token_ttl_seconds, e.g. the embedded IAM), else 30 days; never shorter than the session TTL. Must cover the refresh-token lifetime for a global logout. A revocation covers only sessions/tokens issued before it, so a later sign-in is accepted (a user is never locked out by their own logout). Minimum: 1. | |
boolean | no | Migration bridge: also set auth-bridge’s access_token/refresh_token/id_token cookies on cookieDomain, so auth-bridge’s token-broker proxy routes (/connections, /connect, /oauth) resolve the same session when both share the login host. Default false. The native session cookie remains authoritative for Gate One. | |
array of object | yes | One entry per application domain/provider. Only fields that vary per domain are allowed here. |
ssoGroups.sessionStore
RP-side revocation store (back-channel logout, RP logout). Default: bbolt at ./gate-one-state.db, relative to the config file. bbolt is single-writer: a second Gate One instance on the same file is refused. Redis is the shared, multi-instance backend and makes revocation visible on the next request on every instance.
| Field | Type | Required | Description |
|---|---|---|---|
string (one of: | no | bolt (default, single instance) or redis (shared, multi-instance, immediate revocation). | |
string | no | bbolt file path. Default ./gate-one-state.db (relative to the config file). | |
string | no | Redis URL for type redis (redis:// or rediss://). Mutually exclusive with sentinel mode. | |
array of string | no | Redis Sentinel addresses (host:port). Required with masterName. | |
string | no | Redis Sentinel master name. Requires sentinelAddrs. | |
string | no | ACL user used to authenticate to the Sentinels themselves (sentinel mode only). Separate from username, which is the Redis data user. | |
string | no | Environment variable holding the Sentinel password (sentinel mode only). Never put the password in this file. | |
boolean | no | Use TLS in sentinel mode (Sentinels and the discovered master). In URL mode use a rediss:// URL instead. | |
string | no | PEM CA bundle for the Sentinel TLS connections. Requires tls=true; empty = system roots. | |
string | no | TLS server name override (useful when the Sentinels are addressed by IP). Requires tls=true. | |
string | no | Redis ACL username. | |
string | no | Environment variable holding the Redis password. Never put the password in this file. | |
integer | no | Redis logical database. Default 0. Minimum: 0. | |
string | no | Shared revocation key prefix. Default cladone:auth:v1:. Must match the embedded IAM’s redis.authPrefix. | |
integer | no | Redis dial timeout in milliseconds. Default 500. Minimum: 0. | |
integer | no | Redis read timeout in milliseconds. Default 300. Minimum: 0. | |
integer | no | Redis write timeout in milliseconds. Default 300. Minimum: 0. | |
integer | no | Redis connection pool size. Default 64. Minimum: 0. |
ssoGroups.providers
One domain/provider inside an SSO group. Shared fields (issuer, clientId, sessionStore, …) are deliberately absent: put them on the group.
| Field | Type | Required | Description |
|---|---|---|---|
string | yes | Provider id used by routes (auth.provider). Must be globally unique. Minimum length: 1. | |
string | yes | Public origin of this domain’s login service. Must be the cookieDomain or one of its subdomains. | |
string | no | Optional full login page URL. Defaults to loginBaseUrl + /login. | |
string | yes | Registrable parent domain for this provider’s session cookie (e.g. app-a.com or app-b.fr). Distinct and non-overlapping within the group; route hosts must be covered by it. Minimum length: 1. | |
string | no | Optional session cookie name. Empty = provider-scoped default. | |
string | no | Optional OIDC callback URL. Defaults to loginBaseUrl + /auth/callback. Must be on cookieDomain or a subdomain. | |
no | Links owned by the authentication connector, displayed in the user badge. account is the IdP’s own account console: NO OIDC standard, so the URL is explicit (Keycloak /realms/<realm>/account/, Entra myaccount.microsoft.com, Google myaccount.google.com, Okta /enduser/settings…). | ||
string | no | MCP access-token audience policy. Empty = scope-only (compatible default). A literal value requires that value in aud. "auto" requires the endpoint’s own protected-resource identifier (recommended, and used with Keycloak audience mappers). | |
string | no | Application to send a bare visit to this provider’s login host root to. Must be a route host of this provider (or a host covered by its cookieDomain). Overrides the group/global fallback for this domain. Prefer a neutral page rather than one application. |
ssoGroups.providers.links
Links owned by the authentication connector, displayed in the user badge. account is the IdP’s own account console: NO OIDC standard, so the URL is explicit (Keycloak /realms/<realm>/account/, Entra myaccount.microsoft.com, Google myaccount.google.com, Okta /enduser/settings…).
| Field | Type | Required | Description |
|---|---|---|---|
no | A link rendered in the expanded user badge. URL: http(s) absolute or a local absolute path (/…); javascript:/data: are refused. target defaults to _blank; rel is forced to noopener noreferrer. |
ssoGroups.providers.links.account
A link rendered in the expanded user badge. URL: http(s) absolute or a local absolute path (/…); javascript:/data: are refused. target defaults to _blank; rel is forced to noopener noreferrer.
| Field | Type | Required | Description |
|---|---|---|---|
string | no | Displayed text. Optional for the connector links (default Account/Services); required for userBadge.links. Maximum length: 64. | |
string | yes | http(s) absolute URL or local absolute path (/…). Maximum length: 2048. | |
string (one of: | no | Link target. Default _blank. |