This guide is the fastest path to running TokenSmith and integrating AuthN/AuthZ in a service.
Start here to find the right guide for your situation:
-
I have an external OIDC provider (Keycloak, Azure AD, Okta, Dex, etc.) and want to use it for authentication
- Follow section 1) Start the token service below
- Details: Token Flows: Upstream OIDC
-
TokenSmith is down or I need emergency access without OIDC
- Use the break-glass local user token flow: Break-glass: Local user tokens
- Details: Token Flows: Local user token
-
I'm setting up service-to-service authentication only (no end-user OIDC)
- Jump to: 1.3) Internal service-to-service only
- Details: Internal service auth guide
-
I'm troubleshooting or debugging something
-
I need to understand token claims and what's in a JWT
- See: Claim Reference
pkg/authn validates TokenSmith JWTs. The expected claim set includes standard JWT claims plus TokenSmith claims such as auth_level, auth_factors, auth_methods, session_id, session_exp, and auth_events. TokenSmith-issued JWTs include RFC 7638 kid thumbprints, and middleware requires a valid RFC 7638 kid header for verification.
If you need normative behavior details, see:
docs/authz-spec.mddocs/authz_contract.md
Generate a default config file:
tokensmith generate-config --config ./config.jsonStart TokenSmith:
tokensmith serve \
--config ./config.json \
--key-dir ./keys \
--oidc-issuer https://issuer.example \
--oidc-client-id your-client-idIf --oidc-client-id or --oidc-client-secret are not provided, TokenSmith falls back to OIDC_CLIENT_ID and OIDC_CLIENT_SECRET.
See full command options in docs/cli-reference.md.
TokenSmith publishes its active public signing keys at:
GET /.well-known/jwks.json
This endpoint returns the JWKS used to validate TokenSmith-issued JWTs.
Current behavior:
- response content type is
application/json - the response body contains a standard
keysarray - each published RSA key includes
kty,use,alg,kid,n, ande
Operational guidance:
- treat
/.well-known/jwks.jsonas the canonical verification endpoint for TokenSmith-issued JWTs - TokenSmith currently does not publish its own OIDC discovery document at
/.well-known/openid-configuration - configure JWT verifiers with the direct JWKS URL
- cache keys according to verifier policy and plan for outage/rotation behavior
Example:
curl -s http://localhost:8080/.well-known/jwks.json | jqFor JWKS validation behavior, caching, and failure semantics, see:
docs/http-endpoints.mddocs/authz-spec.md#6-jwks-caching-and-failure-semanticsdocs/security-notes.md#jwks-caching-and-availability-risks
Use case: When upstream OIDC is unavailable or for initial bootstrapping, you can mint tokens locally without relying on an external provider.
Important: This flow is for break-glass and bootstrap scenarios only, not primary production authentication.
tokensmith serve \
--config ./config.json \
--key-dir ./keys \
--enable-local-user-minttokensmith user-token create \
--subject "admin@example.com" \
--scopes "admin,read,write" \
--enable-local-user-mintThis outputs a JWT that you can use immediately with your services.
- Initial bootstrap: Set up an admin account before external OIDC is configured
- Disaster recovery: Emergency access when external OIDC provider is down
- Testing: Quick token generation without involving a full OIDC provider
Once your OIDC provider is ready:
-
Configure it with TokenSmith (no restart required):
tokensmith oidc configure \ --issuer-url "https://keycloak.example.com/realms/master" \ --client-id "tokensmith" \ --replace-existing
-
Optionally restart TokenSmith without
--enable-local-user-mintto disable the break-glass path
- Local user tokens are issued directly by TokenSmith; they do not reflect upstream OIDC identity
- The local-only admin endpoint is only accessible from
localhost - Audit logs should record who minted local tokens and when
- See Security Notes: Local user minting for full details
Details: Token Flows: Local user token flow
Standalone quick guide:
docs/internal-service-auth.md
If your service only needs internal service-to-service AuthN/AuthZ, you can skip end-user token-exchange details and use this path:
- Start TokenSmith with a durable bootstrap store and refresh store.
- Exec into the TokenSmith container and mint one bootstrap token per caller service.
- Pass each caller service its own
TOKENSMITH_BOOTSTRAP_TOKENandTOKENSMITH_URL. - Have the caller service redeem the token at
POST /oauth/tokenusingpkg/tokenserviceorexample/serviceauth. - In the target service, install TokenSmith AuthN middleware to validate TokenSmith JWTs and build a verified principal.
- Install TokenSmith AuthZ middleware and map routes using either explicit
authz.RouteMapperor path/method style (authz.PathMethodMapperplus Casbin matchers). - Ensure service principals map to the
servicerole in policy/grouping.
podman run -d --name tokensmith \
-p 8080:8080 \
-e TOKENSMITH_ISSUER="http://tokensmith:8080" \
-e TOKENSMITH_PORT="8080" \
-e TOKENSMITH_CLUSTER_ID="cluster-1" \
-e TOKENSMITH_OPENCHAMI_ID="openchami-1" \
-e TOKENSMITH_CONFIG="/etc/tokensmith/config.json" \
-e TOKENSMITH_KEY_DIR="/var/lib/tokensmith/keys" \
-e TOKENSMITH_RFC8693_BOOTSTRAP_STORE="/var/lib/tokensmith/bootstrap" \
-e TOKENSMITH_RFC8693_REFRESH_STORE="/var/lib/tokensmith/refresh" \
-v ./config.json:/etc/tokensmith/config.json:ro \
-v ./tokensmith-data:/var/lib/tokensmith \
<your-tokensmith-image>This is the critical requirement: TokenSmith must be started with a persistent bootstrap store path, and bootstrap-token creation must use that same path.
BOOT_SERVICE_TOKEN=$(podman exec tokensmith \
tokensmith bootstrap-token create \
--subject boot-service \
--audience smd \
--scopes "node:read" \
--ttl 10m \
--refresh-ttl 24h \
--bootstrap-store /var/lib/tokensmith/bootstrap \
--output-format json | jq -r '.bootstrap_token')
METADATA_SERVICE_TOKEN=$(podman exec tokensmith \
tokensmith bootstrap-token create \
--subject metadata-service \
--audience smd \
--scopes "node:read" \
--ttl 10m \
--refresh-ttl 24h \
--bootstrap-store /var/lib/tokensmith/bootstrap \
--output-format json | jq -r '.bootstrap_token')Do not mint one shared bootstrap token and reuse it across multiple services. Bootstrap tokens are one-time-use startup credentials and should map to one caller identity.
podman run -d --name boot-service \
-e TOKENSMITH_URL="http://tokensmith:8080" \
-e TOKENSMITH_BOOTSTRAP_TOKEN="$BOOT_SERVICE_TOKEN" \
<your-boot-service-image>
podman run -d --name metadata-service \
-e TOKENSMITH_URL="http://tokensmith:8080" \
-e TOKENSMITH_BOOTSTRAP_TOKEN="$METADATA_SERVICE_TOKEN" \
<your-metadata-service-image>Each service should receive only the token minted for that service. The subject, audience, and scopes are enforced server-side from the stored bootstrap policy.
Expected TokenSmith success log when a service redeems its bootstrap token:
INF Bootstrap token successfully exchanged for service token subject=boot-service audience=smd token_hash_prefix=17c3cca6 refresh_family_id=...
Expected TokenSmith log when refresh rotation starts happening:
INF Refresh token rotated successfully subject=boot-service audience=smd family_id=... usage_count=1
Common TokenSmith failure logs:
WRN Bootstrap token not found client_ip=... token_hash_prefix=17c3cca6
WRN Bootstrap token already consumed (replay attempt) client_ip=... token_hash_prefix=17c3cca6
ERR Refresh token hash mismatch - replay attempt detected family_id=...
ERR Refresh token family is revoked (replay detected via hash lookup) family_id=...
Expected caller-service logs depend on the service itself. pkg/tokenservice returns errors; it does not emit logs on its own. A service that logs the example flow should show output similar to:
Getting initial service token...
Got token, expires at: 2026-04-14 22:49:10 +0000 UTC
Refreshing token...
Refreshed token, new expiration: 2026-04-14 23:49:10 +0000 UTC
Calling target service...
Successfully called target service!
Common caller-service errors when bootstrap wiring is wrong:
missing bootstrap token: set TOKENSMITH_BOOTSTRAP_TOKEN or WithBootstrapToken
bootstrap token exchange failed after 5 attempts: failed to get token: status=400, body={"error":"invalid_grant","error_description":"The provided token is invalid or has already been used"}
failed to get token: status=429, body={"error":"too_many_requests","error_description":"Too many failed token exchange attempts from this address"}
refresh token expired
Important:
bootstrap-token createmust write policy to the same bootstrap store path used by the running TokenSmith service.- Treat bootstrap token creation as a strictly local operation to the TokenSmith runtime context.
- For Podman Quadlets (common OpenCHAMI deployment), mint via
podman exec tokensmith ...so the same mounted store path is used.
Current examples:
example/serviceauth(service token acquisition/refresh)examples/minisvc/main.go(AuthN + AuthZ middleware wiring)examples/minisvc/policy/(Casbin model/policy/grouping)
Normative requirements for service principals:
docs/authz_contract.md#service-principal-requirementsdocs/authz_contract.md#integration-checklist-services
The TokenSmith service currently exposes these user-facing endpoints:
GET /healthGET /.well-known/jwks.jsonPOST /oauth/tokenPOST /token(alias for the service-token flow)
See docs/http-endpoints.md for request/response formats and failure behavior.
Current startup behavior:
- if
--key-fileis set, TokenSmith loads the existing private key - if
--key-fileis not set, TokenSmith generates an RSA keypair and writesprivate.pemandpublic.pemunder--key-dir - bootstrap token policies are stored in
./data/bootstrap-tokensunless overridden - refresh token families are stored in
./data/refresh-tokensunless overridden
For production, use durable storage paths for the RFC 8693 stores.
TokenSmith supports two common patterns:
- Explicit route mapping (
object,action) viaauthz.RouteMapper - Path/method style using Casbin matchers such as
keyMatch2
The smallest working example that includes both styles is:
examples/minisvc/main.goexamples/minisvc/policy/model.confexamples/minisvc/policy/policy.csvexamples/minisvc/policy/grouping.csv
Reference guide:
docs/casbin-first-guide.md
By default, TokenSmith uses an embedded baseline Casbin model and policy.
To add policy fragments from disk, set one of:
TOKENSMITH_POLICY_DIR(preferred)AUTHZ_POLICY_DIR
Policy fragments are loaded at process startup in lexical filename order. Hot reload is not supported in v1.
Details:
docs/authz_policy.md
Recommended rollout sequence per service:
off: verify middleware wiring and principal extractionshadow: evaluate policy without blocking requestsenforce: block denied requests with stable TokenSmith error response
Operational guidance:
docs/authz_operations.md
Track policy_version from startup logs and deny responses to confirm policy consistency across replicas.
If you use chi-specific middleware diagnostics, expose:
chi.DiagnosticsHandler(mode, policyVersion, policySource)
from:
pkg/authz/chi/diagnostics.go
Recommended operator workflow (including endpoint wiring and rollout checks):
docs/authz_operations.md#diagnostics-endpoint-recommendeddocs/authz_operations.md#rollout-verification-playbook
For new services, use canonical principal helpers:
authz.SetPrincipal(ctx, p)authz.PrincipalFromContext(ctx)
When your service uses authn.Middleware, TokenSmith already writes principal
to both authn and authz context helpers. No additional bridge middleware is
required between AuthN and AuthZ.
Reference details:
docs/context-guide.md