Documentation
AppRole example with VaultClientV2
This example demonstrates a complete AppRole login flow with the typed v2 client.
This example demonstrates a complete AppRole login flow with the typed v2 client.
What the workflow demonstrates
- Prepare Vault by enabling a KV v2 mount at
secret. - Write an application secret at
secret/mysql/webapp. - Enable the
approleauth method. - Create a read-only
jenkinspolicy for that one secret path. - Register an AppRole with short-lived tokens.
- Generate
role_idandsecret_idcredentials. - Log in as an app with those AppRole credentials.
- Read the secret with the app token.
- Prove the policy is least-privilege by asserting that delete returns HTTP
403.
This example uses the shared decorator-based runner and personas described in
examples/README.md.
Local Vault
From the repository root, start only the plain Vault service:
docker compose up -d vault
One Vault instance is enough. You do not need vault_tls or vault_mtls unless
you are specifically testing TLS.
For a fresh Vault state:
docker compose down --volumes --remove-orphans
docker compose up -d vault
Run
Install dependencies from the repository root:
npm install
Then run the example:
NANVC_VAULT_CLUSTER_ADDRESS=http://127.0.0.1:8200 npx tsx examples/app-role/main.ts
The helper defaults to http://vault.local:8200. Use the environment variable
above when vault.local is not mapped on your machine.
Environment
For an existing Vault server, set:
export NANVC_VAULT_CLUSTER_ADDRESS=http://127.0.0.1:8200
export TEST_NANVC_VAULT_AUTH_TOKEN=<root-or-admin-token>
export TEST_NANVC_VAULT_UNSEAL_KEY=<unseal-key>
If the local Vault server is initialized by any example or integration helper, the helper writes a shared cache file under your OS temp directory with:
TEST_NANVC_VAULT_AUTH_TOKENTEST_NANVC_VAULT_UNSEAL_KEY
Shell-exported TEST_NANVC_* variables take precedence over cached values. If
Vault reports invalid token, the cached credentials probably belong to another
Vault instance or an older Docker volume. Export valid TEST_NANVC_* values, or
reset local Vault with the fresh-state commands above.
import assert from 'node:assert';
import type { AdminPersona } from '../common/personas/admin.js';
import type { AppPersona } from '../common/personas/app.js';
import type { OperatorPersona } from '../common/personas/operator.js';
import { VaultClientError } from '../../src/main.js';
import { example, runAs, runExample, workflow } from '../common/workflow/decorators.js';
import type { AppRoleCredentials } from '../common/personas/types.js';
import { assertInstanceOf } from '../common/assert.js';
const CREDENTIALS = {
db_name: 'users',
username: 'admin',
password: 'passw0rd',
};
@example('AppRole authentication example')
class AppRoleExample {
private credentials!: AppRoleCredentials;
@workflow('operator', 'Prerequisites: prepare Vault with an AppRole and a secret')
@runAs({ persona: 'operator' })
public async prepareVault(operator: OperatorPersona<'v2'>): Promise<void> {
await operator.ensureKvMountAvailable('secret');
}
@workflow('admin', 'Configure AppRole and create credentials')
@runAs({ persona: 'admin' })
public async adminWorkflow(admin: AdminPersona<'v2'>): Promise<void> {
await admin.vault.secret.kv.v2.write('secret', 'mysql/webapp', { ...CREDENTIALS }).unwrap();
await admin.vault.auth.enableAuthMethod('approle', { type: 'approle' }).unwrap();
const jenkinsPolicy = [
"# Read-only permission on secrets stored at 'secret/data/mysql/webapp'",
'path "secret/data/mysql/webapp" {',
' capabilities = ["read"]',
'}',
].join('\n');
await admin.vault.sys.policies.acl.write('jenkins', { policy: jenkinsPolicy }).unwrap();
await admin.vault.auth
.registerAppRole('jenkins', {
token_policies: ['jenkins'],
token_ttl: '20m',
token_max_ttl: '30m',
})
.unwrap();
this.credentials = await admin.createAppRoleCredentials('jenkins');
}
@workflow('app', 'Log in with AppRole credentials and check policy permissions')
@runAs({ persona: 'app' })
public async appWorkflow(app: AppPersona<'v2'>): Promise<void> {
await app.vault.auth
.loginWithAppRole({
role_id: this.credentials.roleId,
secret_id: this.credentials.secretId,
})
.unwrap();
const secretResponse = await app.vault.secret.kv.v2.read('secret', 'mysql/webapp').unwrap();
const deleteError: unknown = await app.vault.secret.kv.v2.delete('secret', 'mysql/webapp').unwrapErr();
assertInstanceOf(deleteError, VaultClientError);
assert.strictEqual(
(deleteError as VaultClientError).status,
403,
'Expected a 403 Forbidden error when trying to delete the secret with insufficient permissions',
);
assert.deepStrictEqual(
secretResponse.data,
CREDENTIALS,
'Retrieved secret data does not match the expected value',
);
}
}
runExample(AppRoleExample).catch((error) => {
console.error(error);
process.exitCode = 1;
});Source Files
- README source:
examples/app-role/README.md - Runnable source:
examples/app-role/main.ts
This page is generated from the example README. Edit the source README and run
npm run generate:docsto update it.