CLI
The oidc.pub CLI is a single binary for everything you do from the command line:
- Authenticate and manage services on oidc.pub
- Sync
openid-configurationand JWKS from your own issuer - Run a local OIDC issuer for development and testing
It also serves as the sync worker for Docker and Kubernetes deployments — see the Sync Worker documentation.
Installation
The CLI requires Node.js 22+ or Docker.
npx oidc.pub --help
npx oidc.pub --help
npm install -g oidc.pub
oidcpub --help
npm install -g oidc.pub
oidcpub --help
docker run --rm -it registry.gitlab.com/oidc.pub/cli:latest --help
docker run --rm -it registry.gitlab.com/oidc.pub/cli:latest --help
Anonymous testing and claims debugging
Anonymous services are built for short-lived OIDC testing, relying-party setup, and claim matching/debugging before you create an account or wire up a real issuer. They get a random -anon subdomain, expire after at most 24 hours, and are managed by a one-time secret returned when the service is created.
If you already have an issuer to publish, create and sync an anonymous service in one command:
npx oidc.pub service sync -a --source-url https://issuer.internal --oncenpx oidc.pub service sync -a --source-url https://issuer.internal --onceThis command creates a temporary oidc.pub service, prints its one-time management secret, publishes the issuer's discovery document and JWKS to the anonymous subdomain, and leaves the service available until you delete it or it expires.
For local claims debugging, use the built-in dev issuer's anonymous mode instead:
npx oidc.pub dev issuer --anonymousnpx oidc.pub dev issuer --anonymousThe dev issuer mode creates the anonymous service before choosing its issuer URL, so the JWTs it mints have an iss claim that matches the public -anon discovery endpoint.
Use the local token endpoint to inspect or tune claims:
curl -s -X POST http://localhost:9229/token \
-H "Content-Type: application/json" \
-d '{"sub": "ci-bot", "aud": "my-api", "repository": "acme/app", "ref": "refs/heads/main"}' | jq .curl -s -X POST http://localhost:9229/token \
-H "Content-Type: application/json" \
-d '{"sub": "ci-bot", "aud": "my-api", "repository": "acme/app", "ref": "refs/heads/main"}' | jq .Then point the relying party at the anonymous issuer URL shown by the CLI and adjust its claim rules until the token is accepted. Delete the service from the anonymous dashboard when the test is done, or let it expire automatically.
Try it locally with dev issuer
You don't need a real OIDC issuer to try oidc.pub. The CLI ships with a built-in dev issuer that generates an RSA keypair, serves the discovery endpoints locally, and mints signed JWTs — all auto-synced to your oidc.pub service on startup.
1. Create a service
npx oidc.pub login
npx oidc.pub service create --title "Demo"
# • Service created: Demo
# Subdomain: demo-7xk2npx oidc.pub login
npx oidc.pub service create --title "Demo"
# • Service created: Demo
# Subdomain: demo-7xk2For a disposable, no-login trial, create an anonymous service instead:
npx oidc.pub service create --anonymous --title "Demo"
# • Anonymous service created: Demo
# Subdomain: q8m4p2za-anon
# Expires: 2026-06-10T18:00:00.000Znpx oidc.pub service create --anonymous --title "Demo"
# • Anonymous service created: Demo
# Subdomain: q8m4p2za-anon
# Expires: 2026-06-10T18:00:00.000ZThe command prints the anonymous management secret to stdout. Store it immediately; it is not shown again.
2. Run the dev issuer
Pass --service to derive the issuer URL and sync the discovery document on startup:
npx oidc.pub dev issuer --service demo-7xk2
# • Local issuer running on http://localhost:9229
# • Synced openid-configuration and JWKS to https://demo-7xk2.oidc.pubnpx oidc.pub dev issuer --service demo-7xk2
# • Local issuer running on http://localhost:9229
# • Synced openid-configuration and JWKS to https://demo-7xk2.oidc.pubLeave it running. The issuer URL embedded in tokens is https://demo-7xk2.oidc.pub, so any verifier that trusts that issuer can validate the tokens you mint.
You can also let the dev issuer create and clean up an anonymous service automatically:
npx oidc.pub dev issuer --anonymous
# • Anonymous service created: Anonymous dev issuer 1a2b3c4d
# • Local issuer running on http://localhost:9229
# • Synced openid-configuration and JWKS to https://q8m4p2za-anon.oidc.pubnpx oidc.pub dev issuer --anonymous
# • Anonymous service created: Anonymous dev issuer 1a2b3c4d
# • Local issuer running on http://localhost:9229
# • Synced openid-configuration and JWKS to https://q8m4p2za-anon.oidc.pub3. Mint a token
In a second terminal, request a signed JWT:
TOKEN=$(curl -s http://localhost:9229/token | jq -r .access_token)TOKEN=$(curl -s http://localhost:9229/token | jq -r .access_token)Or mint a token with custom claims:
TOKEN=$(curl -s -X POST http://localhost:9229/token \
-H "Content-Type: application/json" \
-d '{"sub": "ci-bot", "aud": "my-api"}' | jq -r .access_token)TOKEN=$(curl -s -X POST http://localhost:9229/token \
-H "Content-Type: application/json" \
-d '{"sub": "ci-bot", "aud": "my-api"}' | jq -r .access_token)4. Verify against the public JWKS
The discovery document and JWKS are now served by oidc.pub. Inspect them with curl:
export ISSUER_URL=https://demo-7xk2.oidc.pub
curl -s "$ISSUER_URL/.well-known/openid-configuration" | jq .
curl -s "$ISSUER_URL/.well-known/jwks.json" | jq .export ISSUER_URL=https://demo-7xk2.oidc.pub
curl -s "$ISSUER_URL/.well-known/openid-configuration" | jq .
curl -s "$ISSUER_URL/.well-known/jwks.json" | jq .Then verify the token signature using the public JWKS:
npm install jose
node --input-type=module --eval "
import { jwtVerify, createRemoteJWKSet } from 'jose';
const issuer = process.env.ISSUER_URL;
const jwks = createRemoteJWKSet(new URL(issuer + '/.well-known/jwks.json'));
const { payload } = await jwtVerify(process.argv[1], jwks);
console.log('Token is valid. Claims:', payload);
" "$TOKEN"npm install jose
node --input-type=module --eval "
import { jwtVerify, createRemoteJWKSet } from 'jose';
const issuer = process.env.ISSUER_URL;
const jwks = createRemoteJWKSet(new URL(issuer + '/.well-known/jwks.json'));
const { payload } = await jwtVerify(process.argv[1], jwks);
console.log('Token is valid. Claims:', payload);
" "$TOKEN"Any OIDC-aware relying party — Vault, GitHub Actions, AWS STS, GCP Workload Identity Federation — configured to trust the issuer URL shown by the CLI will now accept these tokens.
Sync from your own issuer
Once you have a real OIDC issuer (Vault, GitLab, a Kubernetes API server, your own service), point oidc.pub at it with service sync:
npx oidc.pub service sync \
--service demo-7xk2 \
--source-url https://your-issuer.internal \
--oncenpx oidc.pub service sync \
--service demo-7xk2 \
--source-url https://your-issuer.internal \
--onceDrop --once to keep syncing on an interval. For long-running deployments inside Docker or Kubernetes, use the same binary as a container — see the Sync Worker documentation.
Authentication
login
Authenticate via browser-based OAuth. Opens your browser, completes the login flow, and stores a session token locally.
| Flag | Description | Default |
|---|---|---|
--oidcpub-url <url> | oidc.pub base URL | https://oidc.pub |
--profile <name> | Credential profile | default |
--provider <name> | OAuth provider | github |
whoami
Display the currently logged-in user and session status.
logout
Remove stored credentials for the current profile.
Services
service create
Create a new service on oidc.pub. Requires authentication via login or the OIDCPUB_API_KEY environment variable.
| Flag | Description | Required |
|---|---|---|
--title <title> | Human-readable service title | yes, except with --anonymous |
--service-name <name> | Custom service URL name prefix (Business and Enterprise only) | no |
--description <desc> | Service description | no |
-a, --anonymous | Create a temporary anonymous service without logging in | no |
--ttl-hours <hours> | Anonymous service lifetime, up to 24 hours | no |
service sync
Sync OIDC discovery configuration from a source issuer to oidc.pub. Same engine as the sync worker container.
| Flag | Description | Default |
|---|---|---|
--service <subdomain> | Target service subdomain (required unless using --anonymous) | — |
--service-title <title> | Look up the target service by title instead of passing --service | — |
--source-url <url> | OIDC issuer URL to sync from | — |
-a, --anonymous | Create a temporary anonymous service and sync to it without logging in | false |
--ttl-hours <hours> | Anonymous service lifetime, up to 24 hours | 24 |
--api-key <key> | API key or JWT (alternative to login) | — |
--once | Sync once and exit | false |
--interval <seconds> | Seconds between syncs | 300 |
Service accounts
Service accounts provide programmatic access to manage your services. Commands are available under service-account (aliased to sa). Available on the Team plan and above.
service-account create
Create a service account. A static policy returns a one-time bearer token (printed to stdout); store it immediately, it is never shown again.
| Flag | Description | Default |
|---|---|---|
--name <name> | Service account name (required) | — |
--services <list> | Comma-separated subdomains the account may act on. Use * for all. | * |
--ip-allowlist <list> | Comma-separated IPs/CIDRs to restrict the token | — |
--policy-file <path> | JSON policy document; overrides --services for full control (e.g. OIDC policies) | — |
npx oidc.pub service-account create --name "CI/CD Pipeline" --services "*"npx oidc.pub service-account create --name "CI/CD Pipeline" --services "*"The policy file is validated against https://oidc.pub/schemas/service-account-policy.v1.json. Reference the URL via $schema for editor autocomplete.
service-account list
List your service accounts.
service-account delete <id>
Permanently delete a service account. Any token it issued stops working immediately.
service-account regenerate-token <id>
Regenerate the token for a static service account. The previous token is invalidated atomically; the new token is printed to stdout.
Local development
dev issuer
Run a local OIDC issuer with an in-memory RSA keypair. Serves discovery and JWKS endpoints and mints signed JWTs. With --service, the discovery document and JWKS are pushed to your oidc.pub service on startup.
| Flag | Description | Default |
|---|---|---|
--service <subdomain> | Target oidc.pub service to sync to | — |
--service-title <title> | Look up the target service by title instead of passing --service | — |
--url <url> | Issuer URL embedded in tokens and discovery | derived from --service |
-a, --anonymous | Create a temporary anonymous service, sync to it, and delete it on exit | false |
--ttl-hours <hours> | Anonymous service lifetime, up to 24 hours | 24 |
--port <port> | Local HTTP port | 9229 |
--subject <sub> | Default sub claim | test-user |
--audience <aud> | Default aud claim | — |
--claims <json> | Extra claims as a JSON object | — |
Endpoints served by the local issuer:
| Path | Purpose |
|---|---|
GET /.well-known/openid-configuration | OIDC discovery document |
GET /.well-known/jwks.json | Public JWKS |
GET /token | Mint a JWT with default claims |
POST /token | Mint a JWT with claims from the JSON body |
Profiles
Switch between accounts or environments with --profile. Each profile has its own session token and base URL.
npx oidc.pub login --profile staging --oidcpub-url https://staging.oidc.pub
npx oidc.pub service create --profile staging --title "Test Service"npx oidc.pub login --profile staging --oidcpub-url https://staging.oidc.pub
npx oidc.pub service create --profile staging --title "Test Service"The default profile is default.
Credential storage
Credentials are stored in ~/.config/oidcpub/credentials.json (respects XDG_CONFIG_HOME). The file is created with mode 0600. Session tokens expire after 36 hours.
Legacy Docker sync mode
The latest image is backward compatible with existing sync worker deployments. When OIDCPUB_SERVICE_ID is set and no subcommand is given, the container runs in legacy sync mode automatically. Existing Docker and Kubernetes deployments continue to work without changes after updating the image reference.