Configuration
The IdentityFlow program reads one configuration file at startup. This page lists how it finds that file, every key it reads, and the exact message that stops a start when a value is wrong. To install and start the program, see Install and run IdentityFlow.
Keys and defaults · File search · Secrets · Precedence · Environment
This file configures PostgreSQL, sign-in through your OIDC provider, the
provider that receives those sign-ins, discovery of
Workflow Definitions and hooks under src/, and activation of the
Access Configuration you reviewed. Secrets are ${…} references to environment variables.
# A minimal production configuration for IdentityFlow 0.3.0.## Save it as /srv/identity-flow/identity-flow.conf and start the program with# NODE_ENV=production NODE_CONFIG_FILE=/srv/identity-flow/identity-flow.conf# Relative paths below resolve from the directory of this file.## Every secret is an ${ENVIRONMENT_VARIABLE} reference. The program refuses to# start when one of them is not set.
postgres { host = "db.example.com" database = "identity_flow" username = "identity_flow" password = ${IDENTITY_FLOW_DATABASE_PASSWORD} # Encrypt the connection and verify the server certificate. ssl = true}
auth { oidc { issuer = "https://login.example.com/realms/acme" clientId = "identity-flow" clientSecret = ${IDENTITY_FLOW_OIDC_CLIENT_SECRET} redirectUri = "https://flow.example.com/auth/callback" scopes = ["openid", "profile", "email"] origin = "acme-login" authorizationTtl = "10 minutes" sessionTtl = "8 hours" }}
identity { originProviders { # Sign-ins from auth.oidc.origin go to the provider a hooks file registers # with identity.provider("acme", ...). acme-login = "acme" }}
# Workflow Definitions and hooks files are discovered below src/.workflows { path = ["src"]}
authorization { path = ["."] "include" = ["authorization.ts"]
# Start once without this block: the log line "Access Configuration # Candidate <digest> compiled and left inert" names the digest. Review # authorization.ts, then add the block with that digest. A different # digest stops the start. activate { digest = "<digest from the startup log>" reason = "Activate the reviewed Access Configuration" subject = "identity-flow-deployment" name = "IdentityFlow deployment" email = "identity-flow-deployment@example.com" }}Before you activate in production, follow
Activate the configuration at startup
and set digest to the value you reviewed.
The file is HOCON, a JSON superset: key = value, nested blocks in braces,
comments with # or //. Two details differ from JSON:
includeis a HOCON keyword. Write the key in quotes,"include" = [...], or the file fails to parse withUnexpected token.- Write durations as strings such as
"10 minutes"or"8 hours". A plain number is read as milliseconds. Thechangeskeys take whole numbers of milliseconds.
The authorization block of the minimal file shows the quoted key:
"include" = ["authorization.ts"]- quoted: a HOCON keyword
- must match one module
The loader also accepts .json, .json5, .yaml, .yml, .toml,
.properties, .cjs, .jsox and .json6 files with the same keys.
Set NODE_CONFIG_FILE to the absolute path of your file, including its
extension. The program then reads exactly that file.
The program uses the first file it finds and reads nothing else. Files are never merged, so the file it selects must contain every required key.
It searches in this order:
NODE_CONFIG_FILE. With a known extension, only that path. Without one, the program tries, for each extension,<file>.<env>.local,<file>/<env>.local,<file>/<env>/local,<file>.local,<file>/local,<file>.<env>,<file>/<env>and<file>.<env>is the value ofNODE_ENV. IfNODE_CONFIG_FILEis not set, the same list is tried foridentity-flowin the working directory.- The configuration directory,
NODE_CONFIG_DIR, which defaults toconfigin the working directory:identity-flow.<env>.local,identity-flow/<env>.local,identity-flow/<env>/local,identity-flow.local,identity-flow/local,<env>.local,<env>/local,identity-flow.<env>,identity-flow/<env>,local,identity-flow,<env>anddefault, each with every extension.
A NODE_CONFIG_FILE that does not exist is not an error by itself: the search
continues in the configuration directory. When no file is found, the program
prints [@carv/config] No config file found and stops with
auth must be an object.
Every key not marked as required is optional. Apart from the authorization
block and a top-level authorizer key, the program ignores keys it does not
know without a message, so check the spelling against this table.
| Key | Required | Default | Rule and error message |
|---|---|---|---|
auth.oidc.issuer | yes | Absolute URL of your OIDC issuer. auth.oidc.issuer must be an absolute URL. HTTPS is required, see HTTPS rule: auth.oidc.issuer must use HTTPS outside a loopback deployment. | |
auth.oidc.clientId | yes | Client ID of the confidential client registered for IdentityFlow. auth.oidc.clientId must be a non-empty string. | |
auth.oidc.clientSecret | yes | Supply it as a ${…} reference. auth.oidc.clientSecret must be a non-empty string. | |
auth.oidc.redirectUri | yes | The callback address the provider sends browsers back to: https://<your host>/auth/callback. auth.oidc.redirectUri must use HTTPS outside a loopback deployment. Its scheme also decides whether cookies are Secure. | |
auth.oidc.scopes | yes | Array that contains openid. auth.oidc.scopes must be a non-empty array, auth.oidc.scopes must include openid. | |
auth.oidc.origin | yes | A name you choose for this sign-in source; it is not a provider ID. It must appear in identity.originProviders: identity.originProviders must contain auth.oidc.origin "<origin>". | |
auth.oidc.authorizationTtl | yes | How long a started sign-in stays valid. At most 10 minutes. auth.oidc.authorizationTtl must be a positive duration, auth.oidc.authorizationTtl must be at most 10 minutes. | |
auth.oidc.sessionTtl | yes | How long a session lasts. At most 8 hours; a session is not extended. auth.oidc.sessionTtl must be a positive duration, auth.oidc.sessionTtl must be at most 8 hours. |
A missing key reports the same message as an empty one, for example
auth.oidc.clientId must be a non-empty string. A missing block reports
auth must be an object or auth.oidc must be an object.
auth.oidc.issuer and auth.oidc.redirectUri must use HTTPS. Plain HTTP is
accepted only when NODE_ENV is development or workshop and the URL uses
the literal address 127.0.0.1 or [::1]. The name localhost never
qualifies.
| Key | Required | Default | Rule and error message |
|---|---|---|---|
identity.originProviders | yes | Maps each sign-in origin to a provider ID. A provider ID is a non-empty string of at most 128 characters without control characters. The check covers the format only; it does not confirm that a hooks file registers that provider. identity must be an object, identity.originProviders must not be empty, identity.originProviders.<origin> must be a registered provider id, and the auth.oidc.origin rule above. There is no default provider. |
In the minimal file, the sign-in origin acme-login routes to the provider
acme:
acme-login = "acme"- value of auth.oidc.origin
- provider ID from hooks
The provider itself is registered in a hooks file; see Connect your identity provider.
A top-level authorizer key stops the start with
Request-time authorizer configuration is unsupported; deploy a compiled authorization.ts candidate.
Access rules live in authorization.ts, configured below.
The authorization block names the authorization.ts module that defines who
may start, view, operate and audit workflows, and can activate it at startup.
The module itself is described in
Access control.
| Key | Required | Default | Rule and error message |
|---|---|---|---|
authorization.path | if the block exists | Directory or directories to search. authorization.path must be a non-empty string or array. | |
authorization.include | if the block exists | Glob patterns; together with path they must select exactly one module. Write the key as "include". authorization.include must be a non-empty string or array. | |
authorization.exclude | no | none | Glob patterns to skip. |
authorization.cwd | no | directory of the config file | Base for relative paths. |
authorization.activate | no | nothing is activated | Activates the compiled module at startup. Keys: digest, reason, subject, name, email. |
authorization.activate.reason | if activate | Recorded with the activation. authorization.activate.reason must be a non-empty string. | |
authorization.activate.subject, .name, .email | if activate | The account the activation is attributed to, at auth.oidc.origin. All three are required. On the start that activates, the account is created, or its name and email are updated. | |
authorization.activate.digest | no | none | The digest of the module you reviewed. When it differs from the compiled module, the start stops with Access Configuration Candidate <digest> does not match the reviewed digest <digest>; review the module and update authorization.activate.digest. |
Any other key in authorization stops the start with
authorization contains unsupported fields; in authorization.activate, with
authorization.activate contains unsupported fields.
If no Access Configuration has ever been activated in the database, every access function denies: signed-in accounts see an empty workflow catalog and no instances. The startup log line for this block is described in Install and run IdentityFlow.
| Key | Required | Default | Rule and error message |
|---|---|---|---|
workflows.cwd | no | directory of the config file | Base for relative paths. |
workflows.path | no | "." | Directory or directories to search. |
workflows.include | no | **/*.{request,approval,flow,workflow,wf}.?(m)[jt]s, **/workflows/*.?(m)[jt]s | Glob patterns. Write the key as "include". |
workflows.exclude | no | none | Glob patterns to skip, in addition to the patterns below. |
workflows.dangerouslyAllowWorkflowVersionOverwrites | no | false | See the warning. |
These files are never loaded, in workflows, hooks or authorization:
type declarations (*.d.ts), files named *.binding.*, *.config.*,
*.graphql.*, *.test.*, *.spec.*, *.specs.*, *.stories.* or
*.story.*, anything below bindings/, config/, graphql/, test/,
tests/, fixtures/, node_modules/, dist/, build/ or cypress/, every
file or directory whose name starts with a dot, and tool configuration files
such as vitest.config.ts. Workflow discovery also skips hooks files.
A Workflow Definition is deployed only when it sets draft: false; see
Deploy Workflow Definitions.
workflows.dangerouslyAllowWorkflowVersionOverwrites = true lets a changed
bundle replace an already deployed name and version instead of stopping the
start. It takes effect in production mode too. Instances that started on the
old code then replay against different code, which breaks the guarantee that
the same events produce the same state. Leave it unset outside a development
machine.
| Key | Required | Default | Rule and error message |
|---|---|---|---|
hooks.cwd | no | workflows.cwd | Base for relative paths. |
hooks.path | no | workflows.path | Directory or directories to search. |
hooks.include | no | **/*.hooks.?(m)[jt]s, **/hooks/*.?(m)[jt]s | Glob patterns. Write the key as "include". |
hooks.exclude | no | none | Glob patterns to skip. |
| Key | Required | Default | Rule and error message |
|---|---|---|---|
postgres.host | no | PGHOST, then localhost | Host name or address. |
postgres.port | no | PGPORT, then 5432 | |
postgres.database | no | PGDATABASE, then the user name | |
postgres.username | no | PGUSERNAME, then PGUSER, then the operating system user | |
postgres.password | no | PGPASSWORD | Supply it as a ${…} reference. |
postgres.ssl | no | false, or the PGSSL variable | true encrypts the connection and verifies the server certificate against the certificate authorities built into Node.js, not the operating system’s store. For a private certificate authority, set NODE_EXTRA_CA_CERTS to its PEM file when you start the program, or pass an object of Node.js TLS options such as ca. "require" and "prefer" encrypt without verifying the certificate. |
postgres.path | no | none | Full path of the Unix socket file, for example "/var/run/postgresql/.s.PGSQL.5432". To name only the socket directory, set postgres.host = "/var/run/postgresql". |
The block also passes other connection options of the PostgreSQL client
through, such as max, idle_timeout and connect_timeout.
A value in the file beats the PG* variable, but only when it is not empty:
host = "" still uses PGHOST. PGURI is not read. The connection always
reports the application name IdentityFlow to PostgreSQL.
| Key | Required | Default | Rule and error message |
|---|---|---|---|
principalCacheTtl | no | "15 minutes" | How long the principals a provider reported for an account are reused for reads. Commands always ask the provider again. 0 or "0 milliseconds" turns the cache off: reads ask the provider every time. Invalid principal cache TTL. |
These settings control the background work that applies
change events and recomputes
dialog assignees. Every value is a positive whole number; otherwise the start
stops with Change worker options must be positive integers.
| Key | Default | Meaning |
|---|---|---|
changes.concurrency | number of CPU cores | Jobs processed at once. |
changes.pollInterval | 30000 | Fallback interval in milliseconds. New jobs start as soon as they are admitted; this check only picks up work that no notification announced. |
changes.leaseMs | 30000 | Milliseconds a job stays claimed by one worker. |
changes.maxAttempts | 8 | Attempts before a job fails for good. A failed assignee source job appears under Failed work. |
changes.drainTimeout | 10000 | Milliseconds to finish running jobs during shutdown. |
These top-level keys tune how instances are run.
| Key | Default | Rule and error message |
|---|---|---|
concurrency | 3 × number of CPU cores | Instances run at once. Concurrency must be a positive integer. |
pollInterval | "30 seconds" | How often the program checks for instances that are due. Invalid poll interval. |
stepConfig | { retries = { limit = 5, delay = "1 minute", backoff = "constant" }, timeout = "15 minutes" } | Default retries and timeout for every step that does not set its own. 0.3.0 stores timeout but does not enforce it; see Retry boundary. |
removeEnginesOlderThan | "5 minutes" | After this long without a heartbeat, a program that crashed or stopped is removed from the database and its instance locks are released. Invalid dead engine timeout. |
maxBindings | 100 | Binding clients kept in memory. Invalid max bindings. |
Keep secrets out of the file. Write ${NAME} where a secret belongs and set
the environment variable NAME when you start the program:
${NAME}must be set. If it is not, the start stops withUnable to resolve 'NAME'.${?NAME}is optional. If it is not set, the key is left out.
Never put a credential into a Workflow Definition, an event payload or a screenshot. Events are immutable, so a secret that reaches one cannot be removed.
The program also reads .env, .env.local, .env.<NODE_ENV> and
.env.<NODE_ENV>.local from its working directory, or from NODE_ENV_DIR if
set. A variable already set in the process environment wins over these files.
At startup the program expands $NAME and ${NAME} inside every
environment variable, including variables set directly in the process
environment, and writes the result back. A secret that contains $ arrives
changed, and escaping it with \$ does not help. Choose secrets without $.
- One configuration file applies, the first one found. Nothing is merged across files.
- Inside that file,
${NAME}first resolves a value in the configuration; if none exists, it reads the process environment. - Process environment variables win over
.envfiles. - For
postgres, a non-empty value in the file wins over the matchingPG*variable.
| Variable | Purpose |
|---|---|
NODE_ENV | Mode. The program runs in production mode unless NODE_ENV says otherwise. development and workshop allow plain HTTP to 127.0.0.1 for sign-in. Also part of the file names the search tries. |
NODE_CONFIG_FILE | Path of the configuration file. |
NODE_CONFIG_DIR | Configuration directory, default config. |
NODE_ENV_DIR | Directory of the .env files, default the working directory. |
HOST, PORT, ORIGIN, PROTOCOL_HEADER, HOST_HEADER, ADDRESS_HEADER, XFF_DEPTH, BODY_SIZE_LIMIT, SHUTDOWN_TIMEOUT | The HTTP server; see Start the program. |
PGHOST, PGPORT, PGDATABASE, PGUSERNAME, PGUSER, PGPASSWORD | PostgreSQL connection values the postgres block does not set. |
See Troubleshooting when a sign-in origin or provider does not resolve.