Stand up a personal OIDC issuer in Azure and mint JWTs of arbitrary shape against it.
jotsmith is a single-user CLI for testing OIDC / workload-identity federation
flows — octo-sts, HashiCorp Vault JWT auth, AWS IAM OIDC, GCP Workload Identity
Federation, or anything else that trusts a JWKS-publishing issuer. Instead of
exfiltrating a real token from a GitHub Actions runner or a Kubernetes pod, you
publish a discovery document + JWKS to an Azure Storage static website and mint
short-lived tokens with exactly the claims you want.
The signing key lives in Azure Key Vault and never leaves it — signing happens inside Key Vault, so no private key material ever touches your machine.
jotsmith (local CLI)
│
│ data plane
├────────────────────────────┐
│ write blobs │ sign(digest) ⇄ signature
▼ ▼
┌───────────────┐ ┌────────────────┐
│ Storage │ │ Key Vault │
│ $web/ │ │ signing-key │
│ .well-known/│ │ (RSA 2048) │
│ openid-… │ └────────────────┘
│ jwks.json │
└───────┬───────┘
│ HTTPS (public)
▼
Consumer fetches discovery + JWKS to verify
tokens that jotsmith minted.
- Issuer URL is the raw Azure static-website endpoint
(
https://<account>.z<n>.web.core.windows.net). No custom domain in v1. - Algorithm is
RS256only, with one active RSA 2048 signing key. - The tool never provisions Azure resources. Your Storage Account and Key
Vault must already exist;
jotsmith setuponly configures them.
-
An Azure subscription with two existing resources:
- A Storage Account (general-purpose v2).
- A Key Vault in Azure RBAC mode (not legacy access-policy mode).
-
An Azure identity (CLI login, managed identity, etc.) resolvable by
DefaultAzureCredential, holding these roles:Resource Built-in role Why Storage Account Storage Account Contributor Enable static website hosting (only if not already enabled) Storage Account Storage Blob Data Contributor Read/write the published documents Key Vault Key Vault Crypto Officer Create, read, and sign with the key If static website hosting is already enabled, you don't need Storage Account Contributor —
setupdetects and accepts that.
Download a release archive for your platform from the
Releases page, extract it,
and put the jotsmith binary somewhere on your PATH.
# macOS (Apple Silicon) example
tar -xzf jotsmith_*_darwin_arm64.tar.gz
install -m 0755 jotsmith ~/.local/bin/jotsmith
jotsmith --versionPrebuilt archives are available for macOS (amd64/arm64), Linux (amd64/arm64), and Windows (amd64/arm64).
Or build from source (Go 1.25+):
go build -o jotsmith ./cmd/jotsmithjotsmith uses DefaultAzureCredential. The simplest path is the Azure CLI:
az loginPoint jotsmith at your existing Storage Account and Key Vault. This enables
static website hosting (if needed), creates the signing key, publishes the
discovery document and JWKS, and writes your local config file.
jotsmith setup \
--subscription <subscription-id> \
--storage-account <storage-account-name> \
--key-vault <key-vault-name>setup is idempotent — re-running it with the same arguments refreshes the
published documents without recreating the key. The config file is written to
${XDG_CONFIG_HOME:-$HOME/.config}/jotsmith/config.json.
jotsmith token mint --sub octo-sts --aud sigstore --exp 5mThe compact JWT is written to stdout and nothing else, so it's safe to pipe or capture:
TOKEN=$(jotsmith token mint --sub ci --claim repo=acme/app --claim-json admin=true)Claim flags:
--sub(required),--aud(repeatable),--exp,--iat,--nbf,--jti— the standard claims.--exptakes a duration relative toiat(15m,1h,24h) or an RFC3339 timestamp. Default15m. Tokens longer than 24h require--allow-long-lived.--claim key=value— a custom string claim (repeatable).--claim-json key=<json>— a custom claim parsed as JSON, for numbers, booleans, arrays, and objects (repeatable).--claims-file <path>— merge claims from a JSON file.issalways comes from your config and cannot be overridden.
Add --verbose to print the decoded header and payload to stderr after minting.
Round-trips through the live discovery document and JWKS over HTTPS:
jotsmith token verify "$(jotsmith token mint --sub me)" --aud sigstoreTo inspect any token without verifying it (including tokens from other issuers):
jotsmith token decode "$TOKEN"| Command | What it does |
|---|---|
jotsmith setup |
Configure an existing Storage Account + Key Vault as an OIDC issuer and write the config |
jotsmith token mint |
Mint a signed JWT (writes the token to stdout only) |
jotsmith token verify <jwt> |
Verify a token against the live issuer over HTTPS |
jotsmith token decode <jwt> |
Decode a token's header and payload without verifying |
jotsmith doctor |
Audit Azure state against your config (read-only) |
jotsmith doctor --repair |
Fix drift it knows how to fix in place |
jotsmith key rotate |
Rotate the signing key (snap-cutover) |
jotsmith discovery show |
Print the discovery document as it would be published |
jotsmith jwks show |
Print the JWKS as it would be published |
jotsmith config show |
Print the resolved config (or its path with --path) |
jotsmith destroy |
Tear down the issuer-shaped state (keeps the Azure resources) |
jotsmith completion <shell> |
Emit a shell completion script (bash, zsh, fish, powershell) |
Global flags accepted on every command:
| Flag | Env | Default | Purpose |
|---|---|---|---|
--config |
JOTSMITH_CONFIG |
XDG config path | Path to the config file |
--log-level |
JOTSMITH_LOG_LEVEL |
info |
error, warn, info, debug, or trace |
--no-color |
NO_COLOR |
off | Disable ANSI color in stderr output |
Everything except a minted token is written to stderr, so token mint
output stays clean for piping.
Start with doctor. It audits every part of the issuer and reports
PASS / WARN / FAIL per check:
jotsmith doctor # read-only audit
jotsmith doctor --e2e # also mint + verify a throwaway token end to end
jotsmith doctor --repair # fix what it can in place
jotsmith doctor --json # machine-readable outputdoctor --repair can re-enable static website hosting and re-upload the
discovery document and JWKS when they're missing, malformed, or stale. Problems
that need human action (see below) are reported but not changed.
Common issues:
- "Key Vault is in legacy access-policy mode."
jotsmithonly supports Key Vaults in Azure RBAC mode. Migrate the vault to RBAC and re-runsetup. mintfails with a permissions / signing error. Confirm the resolved identity still holds Key Vault Crypto Officer on the named vault. Run with--log-level debugto see which credential and tenant resolved.setupfails uploading the documents with403 AuthorizationPermissionMismatch. The identity can reach the Storage Account but lacks the data-plane role. Grant it Storage Blob Data Contributor on the account — this is a separate assignment from the control-plane access that letssetupenable static website hosting. RBAC role assignments can take a few minutes to propagate.- Wrong Azure identity resolved.
DefaultAzureCredentialchecks several sources in order. Run with--log-level debugto see which one won, andaz login(or set the appropriate environment) to select the right one. verifyrejects a freshly minted token. Check the published documents are current withjotsmith doctor; a stale JWKS after a manual change or a key rotation is the usual cause.doctor --repairre-publishes them.- Storage web endpoint changed (e.g. region migration). If the static-website
URL no longer matches your config's issuer,
doctorflags it but won't rewrite it silently — that would break every consumer's trust policy. Re-runjotsmith setup --force-issuer-rewritedeliberately if the change is real. - A consumer complains about a missing endpoint. The discovery document
intentionally omits
authorization_endpoint,token_endpoint, etc. — there's nothing to point them at. This matches what real workload-identity issuers (GitHub Actions, etc.) publish. Strict OIDC clients may object; that's expected for this testing tool.
jotsmith key rotateThis is a snap-cutover: the moment rotation completes, every token minted
under the previous key stops verifying. There is no overlap window. It prompts
for confirmation unless you pass --yes.
Tear down the issuer-shaped state while keeping your Azure resources:
jotsmith destroyThis soft-deletes the signing key in Key Vault and deletes the published
/.well-known/ documents. It does not delete the Storage Account or Key
Vault themselves. The local config file is kept unless you pass --all:
jotsmith destroy --all # also remove the local config filedestroy prompts for confirmation unless you pass --yes, and re-running it
against an already-destroyed issuer is a safe no-op.
To fully remove everything afterward, delete the Storage Account and Key Vault
through Azure yourself (and purge the soft-deleted key if you don't want it
recoverable). jotsmith never deletes those resources for you.