This package provides convenient helpers for integrating SPIFFE workload identities into TypeScript applications. Instead of dealing with Workload API protocol details, you can enjoy ready-to-use credentials and trust bundles.
The package is published as @jeengbe/spiffe. Versions follow Semantic Versioning.
The client connects to the Workload API over gRPC. If no socket is provided, the client will attempt to connect to process.env.SPIFFE_ENDPOINT_SOCKET, or fall back to unix:///tmp/spire-agent/public/api.sock.
const spiffe = new SpiffeClient();To specify a socket explicitly:
const spiffe = new SpiffeClient('unix:///path/to/api.sock');For advanced gRPC configuration, pass a GrpcOptions object instead:
const spiffe = new SpiffeClient({
host: 'unix:///path/to/api.sock',
channelCredentials: ChannelCredentials.createInsecure(),
meta: { 'workload.spiffe.io': 'true' },
});SpiffeClient implements AsyncDisposable, so you can use await using:
await using spiffe = new SpiffeClient();SpiffeClient implements the SpiffeJwtClient interface.
Use getJwt() in client applications to fetch a JSON Web Token for the specified audience:
declare const spiffe: SpiffeJwtClient;
async function fetchData(url: string) {
const token = await spiffe.getJwt('orders-api');
return fetch(url, {
headers: { authorization: `Bearer ${token}` },
});
}Use getJwtSvid() instead to also get the SPIFFE ID and expiration time:
const svid = await spiffe.getJwtSvid('orders-api');
console.log(svid.spiffeId, svid.token, svid.expiresAtMs);On the server, use validateJwt() to validate an incoming JWT-SVID bearer token. Returns null if the token is invalid.
declare const spiffe: SpiffeJwtClient;
async function authenticateRequest(req: Request) {
const token = extractBearer(req.headers['Authorization']);
const svid = await spiffe.validateJwt('orders-api', token);
if (!svid) {
throw new Error('Unauthorized');
}
return svid; // { spiffeId, claims }
}Both getJwt() and getJwtSvid() accept an optional hint parameter to select a specific SVID when the agent issues more than one:
const token = await spiffe.getJwt('orders-api', 'my-service');SVIDs are cached for half of their remaining TTL and concurrent requests for the same audience are deduplicated.
SpiffeHelper manages JWT-SVIDs on disk with automatic refresh, useful for applications that read credentials from a file path (e.g. some gRPC implementations):
const helper = new SpiffeHelper(spiffe);
const handle = await helper.ensureJwtOnDisk('/tmp/svid.jwt', 'orders-api');
console.log(handle.path); // '/tmp/svid.jwt'
console.log(handle.spiffeId); // current SPIFFE ID
// The file is refreshed automatically at 50% of its TTL.
// Clean up when done:
await handle.close();Files are written with 0600 permissions. Both SpiffeHelper and JwtSvidDiskHandle implement AsyncDisposable.
getJwt() and getJwtSvid() throw NoSvidError when the Workload API returns no SVIDs:
import { NoSvidError } from '@jeengbe/spiffe';
try {
const token = await spiffe.getJwt('orders-api');
} catch (err) {
if (err instanceof NoSvidError) {
// No identity
}
}validateJwt() returns null for invalid tokens rather than throwing.
The @jeengbe/spiffe/google-auth entry point integrates SPIFFE with Google Cloud's Workload Identity Federation.
maybeCreateGoogleAuthFromSpiffeAdc() reads your Application Default Credentials and checks whether they contain a credential_source.spiffe field. This library introduces a non-standard extension to signal that a SPIFFE SVID should be used as the subject token. If that field is present, it returns a configured GoogleAuth instance that fetches tokens from SPIFFE; otherwise it returns undefined, so you can fall back to the standard ADC flow.
import { maybeCreateGoogleAuthFromSpiffeAdc } from '@jeengbe/spiffe/google-auth';
import { BigQuery } from '@google-cloud/bigquery';
const googleAuth = await maybeCreateGoogleAuthFromSpiffeAdc();
const bigQuery = new BigQuery({ authClient: googleAuth });To use this, create an ADC file in the standard external_account format and add a credential_source.spiffe block. The credential_source.spiffe field is not part of the official ADC spec. It is interpreted by this library and ignored by other tooling.
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/<number>/locations/global/workloadIdentityPools/<pool>/providers/<provider>",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"credential_source": {
"spiffe": {
"hint": "external-gcp"
}
}
}The hint field is optional and selects which SVID to use when the agent issues more than one. Only the urn:ietf:params:oauth:token-type:jwt subject token type is supported.
ADC is discovered from GOOGLE_APPLICATION_CREDENTIALS (or google_application_credentials) or the standard gcloud paths (~/.config/gcloud/application_default_credentials.json).
The @jeengbe/spiffe/kafkajs entry point provides helpers for authenticating KafkaJS clients and related services using SPIFFE JWT-SVIDs.
Use createKafkajsSaslMechanism() to create a KafkaJS-compatible SASL OAuthBearer configuration. Pass it directly to the sasl option when constructing a Kafka instance:
import { createKafkajsSaslMechanism } from '@jeengbe/spiffe/kafkajs';
import { Kafka } from 'kafkajs';
const kafka = new Kafka({
brokers: config.kafka.brokers,
sasl: createKafkajsSaslMechanism('kafka-cluster'),
});To pass SASL extensions (e.g. for Confluent Cloud logical cluster routing):
sasl: createKafkajsSaslMechanism('kafka-cluster', {
logicalCluster: 'lkc-abc123',
identityPoolId: 'pool-xyz',
}),Use createKafkajsAuthMiddleware() to create a Mappersmith middleware that attaches a SPIFFE JWT-SVID as a bearer token on outgoing requests. This is useful for authenticating against services like the Confluent Schema Registry:
import { createKafkajsAuthMiddleware } from '@jeengbe/spiffe/kafkajs';
import { SchemaRegistry } from '@kafkajs/confluent-schema-registry';
const schemaRegistry = new SchemaRegistry({
host: config.kafka.schemaRegistry.url,
clientId: config.kafka.schemaRegistry.clientId,
middlewares: [createKafkajsAuthMiddleware('confluent-cloud')],
});To pass additional headers alongside the Authorization header (e.g. for Confluent Cloud logical cluster routing):
middlewares: [createKafkajsAuthMiddleware('confluent-cloud', {
'target-sr-cluster': 'lsrc-abc123',
'identity-pool-id': 'pool-xyz',
})],