Module 5: Credential And Secrets Security
2. How n8n stores credentials
Description
By the end of this lesson you'll be able to explain, with an operator's precision, what an n8n credential is on the inside: where it physically lives, which part of it travels in a workflow's JSON and which part never leaves, and what exactly it means for it to be encrypted at rest. You'll get to know the two-key model that n8n uses today —an instance key that protects a data key, and why that separation is what makes it possible to rotate the encryption without disasters. And you'll face the point where this model breaks if you're careless: what you see and what you don't see when you export your credentials, including the export command option that turns a routine backup into the most dangerous file on the server.
This matters because almost every decision in the following lessons depends on understanding this model well. When you can share a file without worrying and when you can't. Why a credential can't just be copied from one instance to another. Why the Code node doesn't need —and shouldn't— see a token. Why a database backup without the key is a safe with no combination. All of that comes out of the storage model, and whoever isn't clear on it makes decisions by intuition, which in security is a poor advisor.
Connection to the module: lesson 1 established that the credential is the asset and built Terra Market's inventory. This lesson opens the credential up and looks at what's inside, which is what lets you reason about everything else. Lesson 3 will operate on the content of that credential —the permissions it carries; lessons 4 and 5 will change where its value comes from; lesson 6 will decide who can see it; and lesson 8 will verify that all of this turned out well. An important boundary right away: setting up the encryption key —generating it, placing it, backing it up, what happens if it's lost— is developed in depth in the Self-Hosting and Operations guide, module 3, lesson 5, and we're not going to repeat it. Here we take for granted that the key is well placed and we go deeper into the model: how a credential is stored, what gets encrypted with what, and what comes out in an export.
The safe deposit box and the receipt
Let's start with the image, because it's the one that separates the two things everyone confuses.
When someone rents a safe deposit box at a bank, they take two very different things home. They take a receipt with the box number, the holder's name, and the branch. And they leave the contents of the box at the bank: what really matters.
The receipt can be photocopied, left on the table, sent by mail. It opens nothing. It says "there's a box number 412 in Ana's name at the downtown branch," and that, on its own, is no use to anyone for taking anything. The contents, on the other hand, never leave the bank; to get to them you have to go physically, identify yourself, and use the key.
An n8n credential works the same way, and this is the central distinction of the whole lesson:
- The reference is the receipt: the credential's identifier and name. It lives inside the workflow's JSON, travels in every export, gets versioned in the repository, appears in every copy. And it opens nothing.
- The value is the box's contents: the literal ERP key, the password, the token. It lives only inside the n8n instance, encrypted, and doesn't come out in the workflow's JSON or in any copy of it.
Let's see it with Terra Market's order-sync. If you open that workflow's JSON and look for the node that writes to the ERP, you find something shaped like this:
{
"name": "Push order to ERP",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://erp.terramarket.example/api/v1/orders",
"method": "POST",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"id": "17",
"name": "erp_api"
}
}
}
Read the credentials block carefully. It says three things: that this node authenticates with a credential of type httpHeaderAuth, that its internal identifier is 17, and that it's called erp_api. It doesn't say what the key is. No string that opens the ERP appears. That JSON can be pushed to a repository, shared with a colleague, or pasted into a ticket without leaking the secret —as long as the rest of the workflow doesn't have it written down either, which is a separate condition we'll come back to.
The rule that comes out of here, and worth burning in:
The reference travels; the value stays. The workflow says which credential to use, not what the credential is.
Anatomy of a credential
Now let's open the box. An n8n credential, seen as the object the instance stores, has four parts:
1. The identifier. A number or short string that n8n assigns when it's created (17 in our example). It's what the engine uses to find it. It's not secret.
2. The name. The text you give it: erp_api, carrier_api, llm_token. It helps humans recognize it in the list. Also not secret —and that's why it's a good idea for the name to be descriptive and not include, for example, a fragment of the key, something people do more than you'd think.
3. The type. What kind of authentication it is: httpHeaderAuth, oAuth2Api, smtp, and so on. The type determines what fields the credential has. A Header Auth one asks for a header name and a value; an OAuth2 one asks for a client identifier, client secret, and authorization addresses; an SMTP one asks for server, port, user, and password. Also not secret.
4. The data. Here's the secret: the contents of those fields. The literal key, the password, the token, the client secret. This is the only part that gets encrypted, and it's the only one that never comes out in a workflow's JSON.
Think of it like a library card: number, title, and category are visible to anyone in the catalog; what's under lock and key is the content. The first three fields are metadata —information about the credential; the fourth is the credential itself.
This anatomy explains a behavior of the interface that sometimes confuses people. When you open a credential already stored in n8n, you see its name and type normally, but the secret fields show up masked or empty: it doesn't show them back to you. It's not an error or an interface oversight. It's consistent with the model: the metadata is readable, the data is encrypted, and n8n avoids showing it. If you need to change the value, you write it again; you don't read it to copy it.
What "encrypted at rest" means, in plain terms
It's worth defining this term well, because it gets used a lot and understood little.
To encrypt is to transform readable data into an unreadable jumble by means of a mathematical operation that uses a key. The operation is reversible only if you have the key: with it, the jumble goes back to being the original data; without it, it's garbage. The key phrase is reversible with the key: it's not that the data is lost, it's that it's locked away.
At rest means "while it's stored," in contrast to in transit, which means "while it travels across the network." They're two distinct and complementary protections:
- In transit is handled by HTTPS: while the key travels from your browser to the n8n server, or from the n8n server to the ERP, it's encrypted by the channel. That's the browser's padlock, and it's a matter for the Self-Hosting and Operations guide, module 4.
- At rest is handled by n8n's credential encryption: while the key is stored in the database, it's encrypted. That's this lesson.
Think of it like the mail. Putting a letter in a sealed envelope so the mail carrier can't read it is protection in transit. Storing that same letter in a box with a lock when it reaches your house is protection at rest. A sealed envelope doesn't protect a letter you then leave open on the table, and a lock doesn't protect a letter you sent on a postcard. You need both, and neither replaces the other.
Why does encryption at rest matter so much in n8n's specific case? Because of two very realistic scenarios:
Scenario 1: someone gets a copy of the database. It could be a backup that ended up in the wrong place, a disk that was decommissioned without being wiped, or improper access to the Postgres server. Without encryption at rest, that copy would contain Terra Market's keys in plain text: the ERP, the carrier, the AI provider, everything. With encryption at rest, it contains useless jumbles without the encryption key.
Scenario 2: someone with legitimate database access shouldn't see the keys. Terra Market's database administrator needs to do their job —backups, indexes, maintenance— and for that they need access. But their job doesn't include knowing the ERP key. Encryption at rest separates those two things: they can administer the database without being able to read the secrets it stores.
That second scenario is the one that tends to surprise people, and it's an idea worth keeping in mind throughout the module: security doesn't only protect from malicious people on the outside; it also separates responsibilities among legitimate people on the inside. It's not distrust; it's organizational hygiene. Keeping the database administrator from being able to read the ERP key takes a problem off their plate, on top of taking it off yours.
The two-key model
Here comes the part that goes really deep, and that you need to know because it changes how rotation is thought about.
You might imagine that n8n encrypts each credential directly with the N8N_ENCRYPTION_KEY and that's it. That was the original idea and it's still the basic mental model —and it's exactly the one the Self-Hosting and Operations guide teaches when it explains how to set up the key. But it has a huge practical problem, and it's worth seeing it to understand the solution.
The problem: if all the credentials are encrypted directly with the instance key, changing that key means decrypting and re-encrypting absolutely everything, and meanwhile the instance is in a halfway state. Any interruption in that process leaves some credentials encrypted with one key and others with another. It's an open-heart operation, and that's why for a long time the advice was simply "set the key at the start and never change it."
The solution n8n implements today is a classic pattern in cryptography: envelope encryption, or two key levels. It works like this:
- The instance key (
N8N_ENCRYPTION_KEY) is the master key. According to the documentation, it's set at deploy time and doesn't change. Its only job is to protect the other key. - The data key (data encryption key) is the one that actually encrypts the content of your credentials. n8n stores it in the database, itself encrypted with the instance key. This is the one that gets rotated.
The analogy that makes it clear: imagine a big safe in the office. Inside, you don't store the documents directly; you store the keyring for all the filing cabinets on the floor. The documents are in the cabinets, locked with the keys from the keyring. If you want to change the cabinet locks, you don't have to open or touch the big safe: you change the keyring's keys, and the safe stays exactly the same. The big safe —the instance key— is the one that never changes, because its job is to protect the keyring, not the documents.
What does this buy you as an operator? Being able to rotate the encryption of your credentials periodically without touching the instance key and without the open-heart operation. It's the difference between "the encryption key is set once and you pray" and "the data encryption gets renewed every so often, like a password gets renewed."
Rotation, with its warnings
According to the official documentation, encryption key rotation is a feature for self-hosted instances, enabled by the instance owner, and activated with an environment variable:
# .env — enables encryption key rotation.
# Goes on ALL instances: the main process and every worker.
N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION=true
The flow, as the documentation describes it:
- Confirm that all instances —main process and workers— share the same value of
N8N_ENCRYPTION_KEY. You already know this from queue mode: it's the condition for a worker to be able to decrypt what the main process encrypted. - Set the variable on all of them and restart. n8n generates and stores the initial data key by itself.
- Verify in Settings > Data Encryption Keys that the feature became active.
- Rotate when appropriate: from that same screen with the rotate button, or with a
POSTcall to the API's/encryption/keysendpoint.
And now the warnings, which the documentation marks clearly and which are worth repeating because they're the unforgiving kind:
Enabling rotation is a one-way change: there's no going back. The documentation is explicit in asking for a full database backup before enabling it.
After enabling it, don't disable the variable or downgrade the n8n version. Once there's data written in the new format, removing the feature or rolling back the version leaves that data permanently inaccessible.
Read it twice. "Permanently inaccessible" in cryptography isn't a manual's exaggeration: it means the credentials become unreadable garbage and have to be recreated one by one by going into each external system. For Terra Market, with six credentials, it would be a lost afternoon and several uncomfortable conversations. For an instance with fifty, it's a week.
The practical conclusion for an operator: rotation is a good feature and worth having, but you enable it with a verified backup and in a planned window, not on a Tuesday afternoon because it occurred to you. And like everything that changes between versions, confirm the variable name and the flow in the documentation for your version before running it; here we give you the model, not the recipe for your installation.
Worked example: what you see in a credential export
Let's get to the point where the model can be touched with your hands, and where the most people get a surprise.
n8n has command-line commands for exporting and importing credentials. They're used for backups and for moving credentials between instances. We're going to export them two different ways and look at the result, because the difference between the two is the difference between a harmless file and a dangerous one.
Remember: you run these commands on your server; the guide runs nothing.
Step 1 — The normal export. This is the one that exports the credentials as they're stored, that is, encrypted:
# Exports ALL of the instance's credentials to a file.
n8n export:credentials --all --output=backups/credentials.json
Let's break the command down, because every piece matters. n8n export:credentials is the credential export subcommand. --all means "all of the instance's," in contrast to --id=<ID>, which exports a single one. --output= (or its short form -o) indicates the destination file or directory. There are two more flags worth knowing: --separate, which writes one file per credential instead of a single one, and --backup, which is a convenient shortcut —according to the documentation it's equivalent to --all --pretty --separate— designed precisely for backups.
What to expect. A JSON file where each credential appears with its metadata readable and its data as an encrypted jumble. Something shaped like this:
[
{
"id": "17",
"name": "erp_api",
"type": "httpHeaderAuth",
"data": "U2FsdGVkX1+9pQ3nK7bT2mX4yR8vC1dW0aH6sL5eZ3fN8gJ2..."
},
{
"id": "18",
"name": "carrier_api",
"type": "httpHeaderAuth",
"data": "U2FsdGVkX1/kM2xP0wR6nB9tY4uV7cD3fG8hJ1aS5eL0qZ..."
}
]
Notice the erp_api row. The name is in the clear. The type is in the clear. And the data field —where the ERP key lives— is a string that means nothing to whoever reads it. That file, if it falls into the wrong place, is awkward but not catastrophic: without the instance's encryption key, nobody opens it.
Step 2 — The decrypted export, and its warning. There's one more flag:
# ⚠️ DANGER: exports the credentials in PLAIN TEXT.
n8n export:credentials --all --decrypted --output=backups/decrypted.json
--decrypted, according to the documentation, "exports the credentials in plain text format," and the documentation itself accompanies that flag with a direct warning: all sensitive information ends up visible in the files.
What to expect. The same file, with the same structure, but with the data field readable:
[
{
"id": "17",
"name": "erp_api",
"type": "httpHeaderAuth",
"data": {
"name": "X-ERP-Token",
"value": "erp_live_7f4a2c9b0e1d8a5c3b6f9e2d4a7c1b8e"
}
}
]
There's Terra Market's ERP key, in text, in a file on the server. That file isn't a backup: it's the physical copy of your entire keyring. It's worth exactly as much as all your credentials put together, and it has a property they don't: it's not encrypted, so it needs no key to be opened.
It's worth being precise about when --decrypted is legitimate, because the flag exists for real reasons and saying "never use it" would be imprecise. It's justified in two situations: migrating credentials to an instance with a different encryption key —the encrypted export is no use there, because the destination instance can't decrypt it— and recovering a value that got lost somewhere else. Outside those cases, the encrypted export is the one you want.
And if you use it, three rules that aren't optional:
- The file gets deleted as soon as it served its purpose. It doesn't stay "just in case." A
--decryptedthat outlives the task it was created for is a leak waiting to happen. - The file never touches a repository, or a file-sharing service, or a chat. If you have to move it to another machine, it goes over an encrypted and direct channel, not the convenient path.
- The output directory isn't inside the project folder. This slip is more common than it seems: someone exports to
./backups/inside the folder that's under version control, and the file goes into the nextgit add .. If that folder is in the repository, so is the secret.
Step 3 — The verification that closes the exercise. Open the two files and compare them. It's a two-minute exercise and it leaves an impression you don't forget:
# Look at the first one (encrypted): the data is unreadable.
head -20 backups/credentials.json
# Look at the second one (decrypted): the data is out in the open.
head -20 backups/decrypted.json
What to expect: in the first you'll see meaningless strings in the data field; in the second you'll see your real keys. Seeing the difference with your own eyes does more for your operator judgment than any written warning. After that, delete the decrypted file. That's the final step of the exercise, and yes, it's part of the exercise.
What the encryption key does NOT protect
Let's get to the part of the model that avoids false reassurance, because the question "is everything encrypted?" has an answer, and it's no.
The encryption key protects the credentials' data. Period. It does not protect:
The workflows. The logic of order-sync, inventory-update, and shipment-notify is stored readable in the database. It's not an oversight: a workflow's logic isn't a secret in itself, and n8n needs to read it constantly to execute it. What is worth noting is the consequence: if someone gets your database, they get the complete map of your operation —which systems you connect, with which endpoints, with which business rules— even if they don't get the keys. It's valuable information on its own.
The execution data. Here's the hole most people ignore, and it deserves its own paragraph.
Every time a workflow runs, n8n stores the record of that execution: what went in and what came out of each node. With 4,000 daily executions, Terra Market accumulates a mountain of those records. And those records are not encrypted with the credentials key.
Why does it matter? Because if your workflow writes a secret inside an item —for example, a Code node that builds an auth_header: "Bearer erp_live_7f4a..." field— that secret ends up stored in text in the execution history, visible to anyone who can open that execution in the editor. You protected it in the credential and leaked it again through the back door.
This is the deep reason behind a rule you'll see repeated in lesson 5 and in lesson 7, and that you can now justify yourself:
The secret must not pass through the data. It stays in the credential, the node that makes the call uses it, and it never appears in an item. A secret that touches an item stops being encrypted at rest, even if its credential is.
It's worth pointing out that n8n has a feature today to reduce this risk —execution data redaction, which hides the input and output of nodes and keeps only metadata like status and timings— but according to the documentation it's available on Enterprise plans (self-hosted and Cloud) and starting from recent versions. We'll see it in more detail in lesson 6, along with the rest of the plan honesty. For a Community instance like Terra Market's, the defense isn't a feature: it's the discipline of the secret never entering an item.
Your owner account and the user list. Account data is stored with an application's usual protections (passwords aren't stored readable), but that's a different mechanism from credential encryption. Don't mix it up in your mental model.
To have it at a glance:
| What | Does the credentials key encrypt it? | Practical consequence |
|---|---|---|
| A credential's data (key, password, token) | Yes | A database backup without the key reveals no secrets |
| The credential's name, type, and identifier | No | An export shows which credentials exist, not what they're worth |
| The workflows' logic | No | Whoever has the database has the map of your operation |
| Execution history (inputs and outputs) | No | A secret written in an item stays in text |
| User accounts | Another mechanism | Don't mix it with credential encryption |
Common mistakes
Believing that a workflow's JSON contains the secrets (conceptual, and it stalls real work). What happens: someone refuses to push workflows to a repository or share them with a colleague "because they have the keys inside." The team loses versioning and collaboration over a misplaced fear. Why it happens: it's a reasonable precaution in the abstract, and nobody opened the JSON to check. How to spot it: open a workflow's JSON and look for a node's credentials block. If you see only id and name, this is it. How to fix it: the JSON carries the reference, not the value; it can be versioned. What does need reviewing before sharing is that no node has a secret written by hand in a parameters field —a header set manually, a URL with a token inside— because that does travel in the JSON. The rule is "trust the credentials block, distrust the fields someone filled in by hand."
Keeping the decrypted export "just in case" (practical, and serious). What happens: someone runs a --decrypted for a migration, the migration goes fine, and the file stays in the server's backups/ for two years. Why it happens: deleting a backup feels counterintuitive; instinct says backups are kept. How to spot it: search your server for credential JSON files and open them; if the data field is readable, you found one. How to fix it: the decrypted export isn't a backup, it's an unencrypted copy of your keyring. To back up, use the encrypted export together with the encryption key stored separately —that pair is a backup. The decrypted one is created for a specific task and deleted when the task is done. And if you find an old one, besides deleting it, consider those credentials potentially exposed and rotate them.
Backing up the database and believing that backs up the credentials (conceptual, and a classic). What happens: the team has daily Postgres backups and feels covered. One day they restore on a new server and the credentials don't decrypt. Why it happens: the database backup contains the credentials encrypted; without the encryption key, they're worth nothing. It's the safe with no combination. How to spot it: ask yourself where your instance's encryption key is and whether it would survive the loss of the server. If the only copy is in the .env of the machine you back up, you have no backup of the key. How to fix it: the key is backed up separately from the database, in a password manager or the secure place your team uses. The full procedure is in the Self-Hosting and Operations guide, module 3, lesson 5; here it's enough to understand why: database and key are two halves, and a backup that has only one restores nothing.
Writing the secret into an item "just to debug" (practical, and the one that voids the encryption). What happens: someone temporarily puts the token into an output field of a Code node to check that it arrived correctly, sees that it works, and forgets to remove it. The token stays in the execution history of every run. Why it happens: it's the fastest way to debug, and in the moment it feels harmless because "it's just to see." How to spot it: open a recent execution and go through the nodes' outputs looking for strings that look like keys; if your workflow touches a secret, review especially the Code nodes and the Edit Fields. How to fix it: never write a secret into an item, not even temporarily. If you need to check that a credential works, look at whether the call succeeded, not whether the token arrived. And if it already happened, removing the field isn't enough: you have to delete the affected executions and, if the secret could have been seen, rotate it.
Enabling key rotation without a backup and without a window (practical, and no going back). What happens: someone reads about encryption key rotation, thinks it's a good idea, sets the variable on a Tuesday afternoon, and restarts. Everything works… until someone downgrades the version for another reason and the data becomes inaccessible. Why it happens: the variable is easy to set and the immediate effect is invisible, so it doesn't feel like a big change. How to spot it: if your instance has N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION=true and nobody remembers when it was set or whether there was a backup beforehand, this is it. How to fix it: treat it as what the documentation says it is —a one-way change: verified full database backup, planned window, the variable on all instances (main and workers), and the written rule that from then on it's not disabled or downgraded.
Exercises
Exercise 1 — Decide what can be shared. For each file, say whether it can be shared with a colleague over the team's usual channel, and what you'd review first: (a) the exported JSON of order-sync; (b) the result of n8n export:credentials --all; (c) the result of n8n export:credentials --all --decrypted; (d) a full backup of the Postgres database; (e) a screenshot of the output panel of a shipment-notify execution.
See solution
(a) Yes, with a prior review. The workflow's JSON carries the credentials' reference (id and name), not their values. What has to be reviewed first is that no node has a secret written by hand in its parameters: a header set manually, a URL with a token, a value pasted into an Edit Fields. The credentials block is trustworthy; the fields a person filled in are not.
(b) Yes, with reasonable caution. The data goes encrypted: without the instance's encryption key, it can't be opened. Even so it reveals which credentials exist and what type they are, which is reconnaissance information useful to someone malicious. It's not catastrophic to share it, but there's no reason to do it lightly either.
(c) No. It's the plain-text copy of all your keys. The documentation itself warns that all sensitive information ends up visible. This file isn't shared over any channel; it's created for a specific task and deleted when it's done.
(d) Not over the usual channel. It contains the credentials encrypted (protected), but also the complete workflows and the entire execution history in the clear —where there may be Terra Market customer data and, if someone wrote a secret into an item, secrets in text. A database backup moves over controlled channels, not over the team chat.
(e) It depends, and you have to look at it. A screenshot of the output panel shows the data that flowed through the workflow. If customer addresses and phone numbers travel through shipment-notify, that's personal information; and if someone ever put a token into an item, it would be there. The practical rule: before sharing a screenshot of an execution, read it the way someone from outside would read it.
Why it works: the exercise trains the right question, which isn't "is it an n8n file?" but "what exactly does this file contain?" Notice how the answers go from "yes" to "no" according to what the storage model predicts: references and encrypted data can be moved; values in the clear and execution data, not.
Exercise 2 — Explain the two-key model. A colleague asks you: "If the N8N_ENCRYPTION_KEY never changes, how can n8n offer encryption key rotation? Isn't that a contradiction?" Explain the model with an analogy and say what exactly gets rotated.
See solution
One possible answer:
"It's not a contradiction; they're two different keys. The
N8N_ENCRYPTION_KEYis the instance key, and it is indeed set at deploy time and doesn't change. But it's not the one that encrypts your credentials directly: its job is to protect another key, the data key, which is the one that does encrypt the content. That data key lives in the database, encrypted with the instance one. And that's the one that gets rotated.The image that clarifies it: think of a big safe in the office. Inside, you don't store the documents: you store the keyring for all the filing cabinets. The documents are in the cabinets. If you want to change the cabinet locks, you change the keyring's keys, and the big safe isn't touched. The safe —the instance key— never changes because its job is to protect the keyring, not the documents.
What gets rotated, then, is the data key. And it has to be done carefully: enabling the feature is a one-way change, the documentation asks for a full database backup first, and once it's enabled you can't disable it or downgrade the version without leaving the data permanently inaccessible."
Why it works: the answer separates the two keys clearly, explains what the separation is for (being able to rotate without an open-heart operation), and doesn't forget the warnings. An operator who understands the model but omits the "one-way" is one step away from breaking an instance with the best of intentions.
Exercise 3 — Find the leak. This Terra Market Code node passed a review without anyone saying anything, and yet it leaks a secret. Find it, explain exactly where the secret ends up stored, and write the corrected version.
// Node: Code — "Prepare ERP payload"
// Mode: Run Once for All Items
const items = $input.all();
const config = $('Config').first().json;
return items.map((item, i) => ({
json: {
external_id: item.json.order_id,
customer: item.json.customer_name,
total: item.json.order_total,
// So the next node knows where to send it and with what
endpoint: config.erp_base_url + '/orders',
auth_header: 'Bearer ' + config.erp_token,
},
pairedItem: i,
}));
See solution
Where the leak is. In the line auth_header: 'Bearer ' + config.erp_token. The script takes a token —that someone put into a previous Config node— and writes it inside each output item.
Where it ends up stored. In three places, and none of them is encrypted:
- In the node's output panel, visible to anyone who opens the workflow in the editor.
- In the execution history, which n8n stores in the database without the credential encryption. With 4,000 daily executions, that token ends up replicated thousands of times in text.
- In the workflow's JSON, because the
Confignode that contains the token is stored as part of the workflow —so it also travels to the repository if the team versions its workflows.
Notice the irony: the token could be perfectly stored in an encrypted n8n credential, and this script pulls it out of there and publishes it in three different places.
The corrected version. The fix isn't "hide the token better": it's taking it out of the code entirely. The one that makes the call to the ERP is the HTTP Request node that comes after, and that node has its own credentials mechanism. The Code node doesn't need the token —it never needed it— and in n8n 2.0 it can't make the call itself either, so there's no argument for it to touch it.
// ============================================================
// Node: Code — "Prepare ERP payload"
// Mode: Run Once for All Items
//
// INPUT: store orders with order_id, customer_name, order_total
// OUTPUT: one item per order with the body ready for the ERP
// NOTE: this node does NOT know any token or any URL.
// Authentication and destination are handled by the
// following HTTP Request node, with the erp_api credential.
// ============================================================
const items = $input.all();
return items.map((item, i) => ({
json: {
// Only the order data. No secret, no destination.
external_id: item.json.order_id,
customer: item.json.customer_name,
total: item.json.order_total,
},
pairedItem: i,
}));
And the following HTTP Request node is configured with the ERP URL in its URL field and with the erp_api credential in its credentials selector. The token doesn't appear in any item, any panel, or any history.
What to expect when running the corrected version: the output panel shows one item per order with external_id, customer, and total, and no string that looks like a key. That absence is the sign of success.
Why it works: the exercise connects the storage model with an immediate practical consequence. Encryption at rest protects the credential's data field; the moment your code copies that value to an item, the data leaves the protected zone and enters the zone that isn't. That's why the rule isn't "be careful how you handle the token in the code," but "the token doesn't enter the code."
Summary and next step
In this lesson you opened the credential up and looked at its model. With the image of the safe deposit box and the receipt you separated the two things that get confused: the reference —identifier and name, which live in the workflow's JSON and travel everywhere without opening anything— and the value —the secret data, which lives only in the instance, encrypted. You saw the anatomy of a credential in four parts (identifier, name, type, and data) and why only the last one is encrypted, which explains why the interface doesn't show you the secret fields back. You defined encrypted at rest versus in transit, and saw the two scenarios that justify it: a database copy in the wrong hands, and the separation of responsibilities among legitimate people on the inside. You got to know the two-key model —the instance key (N8N_ENCRYPTION_KEY) that doesn't change and protects the data key, which is the one that encrypts your credentials and the one that gets rotated— with the analogy of the safe that holds the keyring, and its serious warnings: rotation is enabled with N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION=true on all instances, it's a one-way change, it requires a prior full backup, and disabling it or downgrading the version afterward leaves the data permanently inaccessible. You compared the normal export with the --decrypted one, whose own documentation warns that all sensitive information ends up visible, and you set the three rules for when it's legitimate to use it. And you closed with what the key does not protect: the workflows, the accounts, and —the hole most ignored— the execution history, from which comes the rule that governs the rest of the module: the secret must not pass through the data.
Before moving on you should be able to: explain what travels in a workflow's JSON and what doesn't; define encryption at rest with an example; explain the two-key model and what exactly gets rotated; say what --decrypted does and when it's justified; and name three things the encryption key does not protect.
Lesson 3 is the heart of the module, and it changes planes: it stops asking where the secret lives to ask what it can do. You'll face Terra Market's most uncomfortable finding —an erp_api with administrator permissions when inventory-update only needs to read— and you'll understand why that gap between what a credential has and what it uses is the one that turns a small incident into a big one. You'll learn to audit a credential for real: who uses it, what permissions it has, when it was rotated, and who answers for it. And you'll come out knowing how to reduce a credential's scope in production without breaking the workflow that depends on it, which is the part that's scary and has a method.
Resources
- Credentials — n8n Docs — what credentials are, how they're created, and how nodes use them. The base reference for this lesson's model.
- Use the command line — n8n Docs — the
export:credentialsandimport:credentialscommands with all their flags, including--decryptedand its warning about sensitive information visible in the files. - Rotate encryption keys — n8n Docs — the two-key model, the
N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATIONvariable, the rotation flow, and the warnings about the one-way change. Confirm the detail in the documentation for your version. - Set a custom encryption key — n8n Docs — the instance key: that n8n generates one automatically in
~/.n8nif you don't set it, and that in queue mode you have to give it to all workers. Its full setup is in the Self-Hosting and Operations guide, module 3, lesson 5. - Redact execution data — n8n Docs — the feature that hides inputs and outputs from the execution history, available on Enterprise plans. Context for the execution data hole; it's picked up again in lesson 6.