Derive tokens
Token derivation creates short-lived JWT or macaroon tokens from a long-lived API key. Use derived tokens when you need:
- Browser-safe credentials — JWTs can be verified client-side without hitting the server.
- Temporary access — grant time-limited access with a subset of the parent key's scopes.
- Custom claims — embed application-specific data in the token.
Derived tokens inherit permissions from the parent API key and can be verified on the same data plane endpoint.
First, issue a parent key for token derivation:
- CLI
- curl
RESPONSE=$(talos keys issue "derive-test" \
--actor user_1 \
--scopes "read,write" \
--format json \
-e "$TALOS_URL" 2>/dev/null)
echo "$RESPONSE" | jq .
export API_SECRET=$(echo "$RESPONSE" | jq -er '.secret')
ISSUE_RESP=$(curl -s -X POST "$TALOS_URL/v2alpha1/admin/issuedApiKeys" \
-H "Content-Type: application/json" \
-d '{"name":"derive-test","actor_id":"user_1","scopes":["read","write"]}')
echo "$ISSUE_RESP" | jq .
export API_SECRET=$(echo "$ISSUE_RESP" | jq -er '.secret')
Derive a JWT
Send the parent key's secret to the derive endpoint with TOKEN_ALGORITHM_JWT:
- CLI
- curl
RESPONSE=$(talos keys derive-token "$API_SECRET" \
--algorithm jwt \
--ttl 1h \
--claims '{"role": "viewer", "tenant": "acme"}' \
--format json \
-e "$TALOS_URL" 2>/dev/null)
echo "$RESPONSE" | jq .
export JWT_TOKEN=$(echo "$RESPONSE" | jq -er '.token.token')
RESPONSE=$(curl -s -X POST "$TALOS_URL/v2alpha1/admin/apiKeys:derive" \
-H "Content-Type: application/json" \
-d "{
\"credential\": \"$API_SECRET\",
\"algorithm\": \"TOKEN_ALGORITHM_JWT\",
\"ttl\": \"1h\",
\"scopes\": [\"read\"],
\"custom_claims\": {\"role\": \"viewer\", \"tenant\": \"acme\"}
}")
echo "$RESPONSE" | jq .
export JWT_TOKEN=$(echo "$RESPONSE" | jq -er '.token.token')
Request fields
The key fields are credential (the parent API key secret), algorithm (TOKEN_ALGORITHM_JWT or TOKEN_ALGORITHM_MACAROON),
optional ttl, scopes (subset of parent's), and custom_claims. For the complete field reference, see the
DeriveToken API reference.
For HTTP API requests, ttl accepts extended formats such as 1y, 1mo, 1w, 1d, and compounds like 1y6mo in addition to
standard Go durations. The current CLI --ttl flag still expects standard Go durations such as 1h or 30m.
Response
The response contains a token object with token.token (the derived token string), token.expire_time, token.scopes, and
token.claims. For the complete field reference, see the
DeriveToken API reference.
Verify a derived token
Derived tokens are verified on the same data plane endpoint as API keys:
- CLI
- curl
talos keys verify "$JWT_TOKEN" -e "$TALOS_URL"
curl -s -X POST "$TALOS_URL/v2alpha1/admin/apiKeys:verify" \
-H "Content-Type: application/json" \
-d "{\"credential\":\"$JWT_TOKEN\"}" | jq .
The verification response includes the token's scopes, actor, and metadata from the parent key.
Derive a macaroon
Macaroons use HMAC-based authentication with support for caveats:
- CLI
- curl
talos keys derive-token "$API_SECRET" \
--algorithm macaroon \
--ttl 30m \
-e "$TALOS_URL"
curl -s -X POST "$TALOS_URL/v2alpha1/admin/apiKeys:derive" \
-H "Content-Type: application/json" \
-d "{
\"credential\": \"$API_SECRET\",
\"algorithm\": \"TOKEN_ALGORITHM_MACAROON\",
\"ttl\": \"30m\"
}" | jq .
JWT vs macaroon
| Feature | JWT | Macaroon |
|---|---|---|
| Verification | Signature-based (can verify client-side with JWKS) | HMAC-based (requires server verification) |
| Size | Larger (base64 JSON + signature) | Smaller (binary format) |
| Client-side verification | Yes, via JWKS endpoint | No |
| Custom claims | Yes | Yes (as caveats) |
JWKS endpoint
For client-side JWT verification, fetch the public keys from the JWKS endpoint at /v2alpha1/admin/derivedKeys/jwks.json:
- CLI
- curl
talos jwk get -e "$TALOS_URL"
curl -s "$TALOS_URL/v2alpha1/admin/derivedKeys/jwks.json" | jq .
The endpoint serves the active public signing keys plus any retired keys still inside the verification window. Each entry includes
a kid field that matches the kid header on tokens signed with that key.
Client-side caching and refresh
Cache the JWKS response on the client so verification does not call Talos on every request. Recommended settings:
| Setting | Recommended value | Notes |
|---|---|---|
| Cache TTL | 5 – 15 minutes | Bounds how long a rotated-out key keeps verifying. |
| Refresh-on-miss | Enabled | If a token's kid is unknown, refetch JWKS once. |
| Refresh failure mode | Serve stale | If the refetch fails, keep the previous keys until TTL. |
Most mature JWT libraries (jose for Node, PyJWT/PyJWKClient for Python, go-jose, jjwt for Java) support these patterns
natively — set the cache TTL and enable refresh-on-unknown-kid. Do not poll the endpoint on a fixed interval shorter than 1
minute; it adds load without changing the practical revocation window, which is bounded by the longest issued token TTL.
When you rotate signing keys, keep the previous key in the JWKS response (mark it retired in the server config, do not delete the
JWK) for at least the longest issued token TTL plus the maximum client cache TTL. Otherwise clients with a freshly cached JWKS
that lacks the new kid can reject valid tokens until their cache expires.
Scope restrictions
Derived tokens can only have scopes that are a subset of the parent key's scopes. If you request any scope that the parent key
does not have, the request fails with a 403 Forbidden error. To restrict scopes, request only scopes that exist on the parent
key.
Next steps
- Issue and verify — create the parent API keys used for derivation
- Key lifecycle — rotate and revoke parent keys
- Self-revocation — allow key holders to revoke their own keys
