0007: CAS tokens are EdDSA JWTs
- Status: accepted
- Date: 2026-10-23
- Scope:
auth,cas
Context
Design §5.4 says CAS tokens are short-lived, signed by openxet or by a platform with a configured key, and verified statelessly on every node. Design §13 left the format and algorithm to the builder.
Stock clients treat the token as opaque and refresh it only by clock,
30 s before exp; they never react to a 401 (S10, "Auth"). The server
therefore has to honour exp exactly.
Decision
-
Format. A JWT signed with EdDSA (Ed25519).
-
Claims.
{sub, scope, mode, exp, iat}:sub: the subject the token acts for.scope: the scope string (design §5.4).mode:"read"or"write";writesupersedesread.exp,iat: Unix seconds.
The header carries
kid, naming the verification key.Amended (M1-R19): the only other claim accepted is an optional
nbf, and the token is rejected whilenow < nbf. Any other claim, such asaud,issorjti, is rejected rather than ignored, so a restriction openxet cannot honour never fails open.subandscopemust be non-empty and free of control characters, andscopemay be at most 1024 bytes. -
Key set. The server accepts several public keys at once and signs with one private key. Rotation adds the new public key everywhere, switches the signer, then removes the old key after the longest token lifetime. A token with an unknown
kidis rejected. -
Expiry.
expis honoured exactly: valid untilexp,401after, with no leeway. -
Static tokens (dev, CI, small setups) are not JWTs. They are compared by SHA-256 in constant time, so timing does not reveal how much of a guess matched.
-
Platforms sign tokens themselves with any standard JWT library against a public key set openxet is configured with. openxet never needs their private key.
Consequences
- Any language can mint tokens, so storage-only platforms need nothing from us beyond a key set.
- Verification is stateless, so any node serves any request (non-negotiable 5).
- Revoking a CAS token before
expis not possible. Lifetimes stay short, and revocation happens at the access token or authorizer (design §5.4). - Tokens carry no revision: Xet reads follow scope membership (R25), so a revision claim would enforce nothing. The Hub token route still accepts a revision in its URL (M2) and ignores it when minting.
- Tests, added with the implementation: expired, wrong-
kid, wrong-algorithm and wrong-mode tokens are rejected; a rotation test accepts old and new keys together.
Alternatives considered
- PASETO v4.public. Rejected: platforms need signers in every language, and JWT libraries are ubiquitous.
- HMAC-signed tokens (HS256). Rejected: verifiers would hold the signing secret, so a platform could not mint tokens without sharing it with every node.