Skip to content
TDACorpIDENTITYSign in

Token command-line tool

Keep a fresh service access token in a file with the tdacorp-identity-token command, and the same behaviour as library functions.

On this page

Some processes need a service token but cannot mint one: a sidecar, a script, a tool that reads a credential from a file. The SDK ships a command, tdacorp-identity-token, that does the minting for them. It runs a service client, writes the access token to a file, and keeps rewriting it before it expires.

The command is installed with the SDK, the package @tdacorp/identity-sdk from the public npm registry. It needs Node 22.12 or later.

What it does#

  1. Builds a service client from your issuer, client id and registered key.
  2. Gets a token for the audience you name: its own client_credentials token by default, or an exchanged token for another service.
  3. Writes the access token to the file you name, atomically.
  4. Unless --once is set, repeats every 15 seconds. The client reuses a cached token until it is within 30 seconds of expiry and then mints a new one, so the file holds an unexpired token and new tokens are minted only when needed.

The write is atomic: the token goes to a temporary file created with permission 0600, is synced to disk and is renamed over the target. A reader never sees a half-written token. If a refresh fails, the command prints the error to standard error and tries again at the next interval; the file keeps the previous token until it expires.

Usage#

tdacorp-identity-token \  --issuer https://identity.tdacorp.in \  --client-id tdaid_c_billing_job \  --key-id k1 \  --private-key-file ./client-key.jwk \  --dpop-key-file ./dpop-key.jwk \  --out /run/secrets/identity-token
OptionRequiredMeaning
--issuer <url>YesThe issuer.
--client-id <id>YesYour service client.
--key-id <kid>YesThe kid of the registered signing key.
--out <path>YesThe file to write the access token to.
--private-key-file <path>One of this or the environmentA private JWK file, Ed25519 or P-256, for client authentication.
--dpop-key-file <path>NoA private JWK file for DPoP. The token is bound to this key. Omit it for a client that proves no key.
--audience <client id>NoThe service the token is for. Default self, the client's own token. Any other value is exchanged.
--scope <scopes>NoSpace-separated scopes.
--onceNoWrite the token once and exit.
--helpNoPrint usage.

The private key can instead be supplied in the environment as JSON. The name of the variable is printed by --help. Give exactly one of the file and the environment, not both. A key is never accepted as a command-line argument, because arguments are visible to other users of the machine.

Without --once the command runs until it receives SIGINT or SIGTERM, then finishes any write in progress and exits.

Exit status#

StatusMeaning
0Success, or a clean stop.
1A runtime failure, for example an unreadable key or a refused token request.
2A usage error: a missing option, an unknown option, both key sources, or no key.

In code#

The same behaviour is available as functions from the SDK's service subpath.

import { createServiceClient, startTokenFileWriter } from '@tdacorp/identity-sdk/service'const service = createServiceClient({ issuer, clientId, key, dpop })const writer = startTokenFileWriter({  path: '/run/secrets/identity-token',  getToken: async () => (await service.tokenFor('self')).accessToken,  refreshIntervalMs: 15_000,  onError: (error) => log.error(error),})// laterawait writer.stop()
FunctionBehaviour
startTokenFileWriter({ path, getToken, refreshIntervalMs?, onError? })Writes now, then at each interval. onError defaults to a line on standard error. Returns { stop() }.
writeTokenFileAtomic(path, token)The atomic write on its own.

Worked example#

A sidecar keeps a token for a job that calls the billing service.

tdacorp-identity-token --issuer https://identity.tdacorp.in --client-id tdaid_c_job \  --key-id k1 --private-key-file ./key.jwk --dpop-key-file ./dpop.jwk \  --audience tdaid_c_billing --out ./billing-token &

The job reads ./billing-token for each call. Because the token is bound to the DPoP key, the job must also hold dpop.jwk to sign a proof for each request; the file alone is not enough to call the service.

Errors#

A refused token request prints the OAuth code, for example invalid_client or invalid_target. See Token and Error codes.

Security notes#

  • Protect the output file. It is created 0600. Do not widen it, and put it on a path only the consuming process can read.
  • Protect both key files the same way. Anyone who reads them can act as the service.
  • Prefer a bound token. Pass --dpop-key-file so a copy of the output file is not enough on its own.
  • Use a short interval and let the file rotate. Do not copy the token elsewhere.

Verified against commit 5d85077