n8n Credentials and Security
Non-negotiables
- Secrets via the credential system, never in text fields or SDK code. API keys, bearer tokens, OAuth secrets, passwords: all go through
newCredential()or the node'scredentialsparameter. A Set node hardcoding a token and read via{{$json.token}}is a text field with extra steps. - List credentials, then bind by ID. Call
list_credentials({type})before configuring an auth-needing node. One match: bind via 2-argnewCredential('Label', 'credId')at create time, orsetNodeCredentialop onupdate_workflow. Multiple matches: ask the user which. The one-argnewCredential('Label')is a placeholder; n8n auto-assigns the most recently edited credential of that type and silently picks wrong when the user has multiples. - Credential creation is the user's job, not yours. The n8n MCP doesn't expose credential creation. Tell the user the exact credential type to create in the UI, then reference it by label in your node config. Don't attempt to create credentials programmatically and don't accept secrets in chat to "set up later".
Strong defaults
- Use native credentials when available. Every native node (Slack, Gmail, Postgres, OpenAI, etc.) has a credential type. Don't reach for generic credential types when a native option exists.
- For multi-header or header-plus-query auth shapes, use the
httpCustomAuthcredential type. Seereferences/CUSTOM_CREDENTIALS.md.
The credential system
In n8n, credentials are first-class objects:
- Stored encrypted at rest in the n8n database.
- Referenced by ID from nodes that need them.
- Scoped to projects (Cloud & enterprise) or shared globally (some self-hosted setups).
- Identified by a type slug (googleSheetsOAuth2Api, slackApi, httpHeaderAuth). The slug is what nodes reference and what determines which auth fields the credential collects.
A node that needs auth has a credentials parameter pointing to a credential ID + type. Secret values never appear in workflow JSON. Exporting a workflow leaks the reference, not the secret.
For the full model (SDK resolution, rotation, project scoping), see references/CREDENTIAL_SYSTEM.md.
Decision tree: how to authenticate this thing
When the user pastes a secret into a chat
This happens. The user types something like:
"Set up a workflow to call Acme API with bearer
sk-abc123def456"
What to do:
- Don't put the token in a text field, even temporarily. A Set node that hardcodes the value and is referenced via
{{$json.token}}is a text field with extra steps. - Bind to an existing credential if possible.
list_credentials({type})first; if a match exists, bind viasetNodeCredentialand tell the user which one you used. If none exists, tell them to create one in the UI (Bearer Auth for bearer tokens, Header Auth for custom headers, etc.). Credential creation is still UI-only. - Treat the pasted secret as compromised, and tell the user to rotate it. Don't soften this. The token has been transmitted to the LLM provider, may persist in chat history, transcripts, and cache layers. Tell them: "Rotate this token as soon as the new credential is set up. Treat it as leaked."
When no native node exists
Common case: the user wants a service n8n has no node for. Use HTTP Request with appropriate auth.
references/FINDING_API_DOCS.md: discovering auth scheme, base URL, common shapes.references/HTTP_REQUEST_WITH_AUTH.md: wiring HTTP Request to a credential.references/CUSTOM_CREDENTIALS.md: when built-in auth types don't fit.


