-
Notifications
You must be signed in to change notification settings - Fork 23
Getting Started
| Node.js | 18 or newer (CI runs the suite on 18, 20, 22, 24) |
| Vault | Any version exposing the v1 HTTP API; KV v1 and KV v2 both supported |
| Optional peer |
config >=1 <4, only if you use fillNodeConfig()
|
npm install node-vault-clientThe package ships CommonJS (main: src/VaultClient.js), so both require() and import work.
VaultClient.boot(name, options) creates a named singleton. Calling it again with the same name returns the same
instance, which is what you want for a long-lived process — one token, one renewal timer.
const VaultClient = require('node-vault-client');
VaultClient.boot('main', {
api: { url: 'https://vault.example.com:8200/' },
auth: { type: 'appRole', config: { role_id: '637c065f-...', secret_id: '...' } },
});
// anywhere else in the process
const vault = VaultClient.get('main');| Static method | Purpose |
|---|---|
VaultClient.boot(name, options) |
Create (or fetch) the named instance |
VaultClient.get(name) |
Fetch an already-booted instance |
VaultClient.clear(name) |
Drop one instance, or all of them when called with no name |
{
api: {
url: 'https://vault.example.com:8200/', // required
apiVersion: 'v1', // default 'v1'
namespace: 'team-a', // Vault Enterprise namespace
engines: { secret: 2, legacy: 1 }, // static mount-version map
kv: { autoDetect: false }, // ask Vault for each mount's version
requestOptions: {}, // merged into every HTTP request
},
auth: {
type: 'token', // 'token' | 'appRole' | 'iam' | 'kubernetes' | 'jwt'
mount: 'token', // default varies per type
renewal: true, // false = never renew in the background
renewalFraction: 0.5, // when to renew, as a fraction of remaining TTL
renewalIncrement: undefined, // seconds of extra TTL to request
config: { /* backend specific, below */ },
},
logger: false, // false disables logging; default is console
}logger accepts any object implementing error, warn, info, debug and trace. Anything missing a method
falls back to console, so pass a complete logger or false.
For local development and CI, where a token already exists.
auth: { type: 'token', config: { token: process.env.VAULT_TOKEN } }The usual choice for VMs and non-cloud workloads.
auth: {
type: 'appRole',
config: {
role_id: '637c065f-c644-5e12-d3d1-e9fa4363af61',
secret_id: '...', // omit for a bind-cidr / secret-id-less role
},
}For EC2, ECS and Lambda. With no credentials block the client uses the AWS default provider chain
(fromNodeProviderChain()), so instance/task/execution roles work with no secrets in your config at all.
auth: {
type: 'iam',
config: {
role: 'my-app',
region: 'us-east-1', // optional
iam_server_id_header_value: 'vault.example.com', // optional, if the role requires it
// credentials: { accessKeyId, secretAccessKey } // optional; omit to use the AWS chain
},
}For pods. The service-account JWT is read from disk on each login, so token rotation by the kubelet is picked up.
auth: {
type: 'kubernetes',
config: {
role: 'my-app',
tokenPath: '/var/run/secrets/kubernetes.io/serviceaccount/token', // default
},
}Availability: the JWT backend is on
masterand ships in the next release; it is not in npm 2.1.2.
For anything that can hand you a JWT: CI jobs (GitHub Actions OIDC), a cloud metadata endpoint, a SPIFFE/SPIRE
workload. Exactly one of jwt / jwtPath / jwtProvider supplies the token; role is optional and falls back
to the mount's default_role when omitted.
auth: {
type: 'jwt',
mount: 'jwt', // default
config: {
role: 'my-app', // optional; falls back to the mount's default_role
jwt: process.env.CI_JOB_JWT, // a literal token string, OR:
// jwtPath: '/path/to/token', // re-read from disk on every login, OR:
// jwtProvider: () => core.getIDToken('vault'), // called fresh at every login
},
}See Recipe 14 for the GitHub Actions OIDC case, and
note that this client only implements the non-interactive jwt login — the browser-redirect oidc flow is out
of scope for a headless service client.
Availability: the three
renewal*keys are not in npm 2.1.2 yet — they land with the next release.
When Vault issues a renewable token the client renews it in the background — at half its
remaining lifetime, for as long as the client lives. That is the default and suits long-running
services. Three optional keys on auth change it:
| key | default | meaning |
|---|---|---|
renewal |
true |
false arms no timer at all: the token is used until it expires, then the next call re-authenticates |
renewalFraction |
0.5 |
Fraction of the remaining lifetime to wait before renewing, in (0, 1)
|
renewalIncrement |
(unset) | Seconds of extra TTL to request, sent as increment to auth/token/renew-self
|
They sit on auth, beside type — not inside auth.config, which is the backend's own credential
bag:
auth: {
type: 'kubernetes',
renewal: false,
config: { role: 'my-app' },
}Before turning renewal off, check your backend can get a fresh credential unaided:
| backend | on expiry with renewal: false
|
|---|---|
kubernetes, iam, jwt with jwtPath/jwtProvider
|
clean re-login — the intended case |
appRole |
replays the same secret_id; fatal if the role uses secret_id_num_uses=1
|
jwt with a literal jwt
|
replays the same JWT, so it works only until the IdP token expires |
token |
cannot re-authenticate at all — raises AuthTokenExpiredError from then on, permanently |
Two more things worth knowing: a login that can no longer succeed is retried on every call with
no backoff, and letting a token expire also revokes the leases it created — this client renews
only the auth token, so dynamic secrets (database/creds/*) whose lease outlives the auth token
depend on renewal being on. See recipe 15.
read() resolves to a Lease, not a bare object:
const lease = await vault.read('secret/app');
lease.getData(); // { DB_PASSWORD: 's3cret', API_KEY: 'k' }
lease.getValue('DB_PASSWORD'); // 's3cret'
lease.isRenewable(); // false
lease.getMetadata(); // { version: 3 } — KV v2 onlyawait vault.write('secret/app', { DB_PASSWORD: 'new' }); // full write
await vault.update('secret/app', { API_KEY: 'k2' }); // merge-patch: only this key changes
await vault.delete('secret/app');
await vault.list('secret');update() issues an HTTP PATCH with Content-Type: application/merge-patch+json, so the other keys in the
secret are left alone. It requires a Vault that supports merge-patch on KV v2.
The renewal timer keeps the event loop alive. Close the client when your process is winding down:
vault.close();Next: Recipes & Use Cases.
node-vault-client · Apache-2.0 · maintained by Namecheap