Skip to main content

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:

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')

Derive a JWT

Send the parent key's secret to the derive endpoint with TOKEN_ALGORITHM_JWT:

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')

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:

talos keys verify "$JWT_TOKEN" -e "$TALOS_URL"

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:

talos keys derive-token "$API_SECRET" \
--algorithm macaroon \
--ttl 30m \
-e "$TALOS_URL"

JWT vs macaroon

FeatureJWTMacaroon
VerificationSignature-based (can verify client-side with JWKS)HMAC-based (requires server verification)
SizeLarger (base64 JSON + signature)Smaller (binary format)
Client-side verificationYes, via JWKS endpointNo
Custom claimsYesYes (as caveats)

JWKS endpoint

For client-side JWT verification, fetch the public keys from the JWKS endpoint at /v2alpha1/admin/derivedKeys/jwks.json:

talos jwk get -e "$TALOS_URL"

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:

SettingRecommended valueNotes
Cache TTL5 – 15 minutesBounds how long a rotated-out key keeps verifying.
Refresh-on-missEnabledIf a token's kid is unknown, refetch JWKS once.
Refresh failure modeServe staleIf 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