Vaults: secrets and environment settings
Keep client secrets, API keys, hosts and client IDs out of your flows. Store them in vaults, read them at run time with Load From Vault, and the same flow runs unchanged on every environment.
The rule: nothing secret or environment-specific in a flow
Anyone who can open a flow, an export or a backup sees every value typed into it. Values that differ per environment also stop a flow from moving from test to production unchanged. So keep both kinds of value in vaults:
- Secrets: client secrets, passwords, API keys, signing keys.
- Environment settings: the tenant host, token and service URLs, client IDs, tenant IDs.
A Load From Vault node reads them while the flow runs. Every environment has vaults and entries with the same names but its own values. A secret typed into a flow is a finding to fix, even in a flow marked test or dev.
Vaults and entries
A vault groups values. An entry is one value, identified by its path (for example identityClientSecret) and the name of its vault. There are two vault types:
| Vault type | For | In the admin console |
|---|---|---|
| Secrets vault | secrets | The value stays hidden. An administrator must select the show-value icon twice to reveal it. |
| Variable vault | non-secret settings | The value shows in clear. |
- 1
Create a vault
Go to Flow Vaults → Vaults, select Add, enter the name, choose the type, set it Active, and select Add.
- 2
Add an entry
Go to Flow Vaults → Entries, select Add, enter the Path and Value, choose the Vault Name, set it Active, and select Add.
- 3
Check a value
In Entries, select the show-value icon next to the entry. For a secrets vault, select it again inside the popup.
- Choose names carefully. A vault's name can't change once it has entries, and an entry's path and vault can never change. Flows refer to both by name.
- Never put an environment or tenant name in a vault name. A flow that reads
acme-test-configbreaks when you promote it. - Flows can't read an inactive entry. Deactivate an entry instead of deleting it if you might need it again.
- You can delete a vault only when it has no entries. Deleted vaults and entries can't be restored.
Use three vaults: secrets, esv and esvFrontend
What matters most is which values may reach the browser. Decide it once per entry, by choosing its vault, instead of in every flow:
| Vault | Holds | Loaded with frontend available | Read as |
|---|---|---|---|
secrets | client secrets, passwords, API keys | never (off) | {{secrets.identityClientSecret}} |
esv | backend settings: tenant ID, client IDs, token and service URLs, hosts | off | {{esv.tokenUrl}} |
esvFrontend | non-secret settings pages may show: feature flags, display settings, banner text | on | {{esvFrontend.showEmail}} |
- 1
Would it hurt if a user saw it in the browser's developer tools?
Put it in
secrets. - 2
Does a page, a response body or a browser log need it?
Put it in
esvFrontend. Everything there is public: never a secret, a client ID or an internal URL. - 3
Otherwise
Put it in
esv. If you aren't sure, chooseesv; you can move it later.
Use the same three names on every environment, so flows move between them unchanged.
Read values with Load From Vault
Add one Load From Vault node per vault, early on the flow's backend path, before the first node that needs a value. Name its variable after the vault, list the entries you need, and set frontend availability as the vault requires:
{ "vault": "secrets",
"keys_to_store": ["identityClientSecret"],
"flow_variable": "secrets",
"frontend_available": false }- Only the entries you list exist afterwards, as
{{secrets.<path>}}. There is no direct vault syntax in templates. - The node has two outputs,
SuccessandFailure. Connect both. - Libraries share variables with the flow that calls them. A Load settings library nested at the start of each journey serves every journey. It can also hold your non-secret constants (a Set Flow Constants node), so you change a limit or a flag in one place.
- Use the same variable name and the same frontend setting in every loader of a vault. If two loaders of
esvdiffer, backend nodes and pages see different values.
Where a loaded value works
Vault values follow the normal rule for flow variables. A value loaded with frontend availability off works on the server and renders as undefined in anything sent to the browser:
| Used in | Frontend available off | Frontend available on |
|---|---|---|
| Backend - HTTP Request URL, headers and body; backend scripts; conditions | the value | the value |
Flow Session - Update variables mapping, quoted: "{{esv.region}}" | the value | the value |
| Page HTML and scripts, end-node bodies, browser logs | undefined | the value |
- Keep
secretsoff and use them only in backend nodes. - If a page needs a setting, move the entry to
esvFrontend. Don't copy anesvvalue into a browser-visible variable: that hides the decision inside one flow. - In a variable mapping, quote the template (
"{{esv.region}}"). A bare template fails for text values.
Example: call a platform API with credentials from the vault
Load From Vault (esv) → Load From Vault (secrets) → Get token → Call the API
└ Failure ┴──── Failure → error pageThe Get token node, a Backend - HTTP Request:
{ "method": "POST",
"url": "{{esv.tokenUrl}}",
"headers": { "Content-Type": "application/x-www-form-urlencoded" },
"data": "grant_type=client_credentials&client_id={{esv.identityClientId}}&client_secret={{secrets.identityClientSecret}}",
"flow_variable": "token", "keys_to_store": ["data"], "frontend_available": false }The API call then sends Authorization: Bearer {{token.data.access_token}}. Check the body as well as the headers: a secret typed into a form-encoded body is just as exposed as one in a header. Which audiences and scopes the client needs: Access clients and Scopes.
Thales service nodes get their own token. Give them the client secret from the same loader: "config": { "clientSecret": "{{secrets.oipClientSecret}}" }.
Mistakes to avoid
| Mistake | What happens | Fix |
|---|---|---|
| A wrong vault name in Load From Vault | The run fails at that node. The failure has no output you can connect. | Check the name under Flow Vaults → Vaults. Names are case-sensitive. |
| An entry not listed in the loader's keys | {{secrets.x}} stays empty | Add the entry's path to the loader. |
secrets loaded with frontend availability on | Secrets can reach the browser | Turn it off. Use secrets only in backend nodes. |
| A vault value in a log node | The secret lands in the logs on every run | Log that a value is present, never the value. |
| A secret in Set Flow Constants | Constants are ordinary variables, visible in the flow and possibly in the browser | Move it to secrets. |
A vault value shown on a page renders undefined | The loader has frontend availability off | Move the entry to esvFrontend, if it is safe to show. |