HWT — Hash Web Token
Verifiable auth tokens between any two services. No central provider required.
import Hwtr from 'jsr:@hwt/hwtr-js'
const hwtr = await Hwtr.factory({}, keyConfig)
const result = await hwtr.verify(token)
if (result.ok) {
console.log(result.data)
// sub "user:4503", authz { scheme: "RBAC/1.0.2", roles: ["editor"] }
}
Read the spec - View demos - Get the library
token format
hwt.signature.key-id.expires.format.payload
Six dot-separated fields. The signature covers expiry, format, and payload in a single canonical pass. Verification reconstructs that input from the token and checks it against the issuer's published key.
Decoded payload — delegated agent token, two-hop chain:
{
"iss": "https://agent-b.example.com",
"sub": "svc:agent-b",
"aud": "https://api.target.com",
"tid": "tok-7c8d",
"authz": { "scheme": "RBAC/1.0.2", "roles": ["editor"] },
"del": [
{ "iss": "https://auth.example.com", "sub": "user:4503", "tid": "tok-a1b2" },
{ "iss": "https://agent-a.example.com", "sub": "svc:agent-a", "tid": "tok-c3d4" }
]
}
The verifier sees the full chain: user delegated to agent-a, agent-a delegated to agent-b. The del array is covered by the outer signature — it cannot be tampered with after issuance. Application-layer state checks (revocation, session validity) are the consuming application's responsibility at each issuer.
Verification
Pre-registered issuers — recommended for production:
Load trusted issuer keys at startup. Subsequent verification is local — no network call per token.
import Hwtr from 'jsr:@hwt/hwtr-js'
// at startup: create an instance with trusted issuer public keys
const hwtr = await Hwtr.factory({}, {
type: 'Ed25519',
keys: [],
publicKeys: {
'auth-key': 'BASE64URL_SPKI', // from auth.example.com
'partner-key': 'BASE64URL_SPKI' // from partner.example.com
}
})
// in your request handler — no network call
const result = await hwtr.verify(token)
if (result.ok) {
console.log(result.data)
// sub "user:4503" authz { scheme: "RBAC/1.0.2", roles: ["editor"] }
}
Signing and full round-trip:
// Generate keys once, store securely
const keyConfig = await Hwtr.generateKeys({ type: 'Ed25519' })
const hwtr = await Hwtr.factory({ expiresInSeconds: 3600 }, keyConfig)
// Sign
const token = await hwtr.create({
sub: 'user:4503',
authz: { scheme: 'RBAC/1.0.2', roles: ['editor'] }
})
// Verify
const result = await hwtr.verify(token)
// result.ok, result.data, result.expires, result.error
The delegation chain
DELEGATION CHAIN FLOW DIAGRAM
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌────────────────────────────────────┐
│ user:4503 │──────────────► auth.example.com
│ iss: auth.example.com │ key publication
│ tid: tok-a1b2 │
└────────────────────────────────────┘
│ delegated to
▼
┌────────────────────────────────────┐
│ svc:agent-a │──────────────► agent-a.example.com
│ iss: agent-a.example.com │ key publication
│ tid: tok-c3d4 │
└────────────────────────────────────┘
│ delegated to
▼
┌────────────────────────────────────┐
│ svc:agent-b │──────────────► agent-b.example.com
│ iss: agent-b.example.com │ key fetch + sig verify
│ tid: tok-7c8d (outer token) │
└────────────────────────────────────┘
│ presents token
▼
api.target.com
Protocol verification (spec §12):
1. Verify agent-b's signature against agent-b.example.com keys
2. Confirm del[] structural integrity — covered by outer signature
3. Hand verified payload to application
Application layer (implementation choice):
4. Check tok-c3d4 state at agent-a.example.com
5. Check tok-a1b2 state at auth.example.com
No step contacts a central server.
The del array is covered by agent-b's signature —
it cannot be tampered with after issuance.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
What HWT does not address
Explicit boundaries matter for security protocols. These are outside the scope of HWT by design.
Token state and revocation. Whether a token has been invalidated after issuance is not a property of the signed byte string. HWT defines what a token structurally guarantees; it does not define session lifecycle or revocation infrastructure. Applications that need immediate invalidation maintain their own state store and check it at the application layer. Short token lifetimes are the primary exposure-bounding mechanism.
Token issuance. How a principal obtains a token is between the principal and the issuing service. WebAuthn/FIDO2 is a natural complement for the authentication step that precedes issuance — also domain-sovereign, no central provider.
Authorization evaluation. HWT carries the claim (roles: ["editor"]). Whether editor permits a specific action is the consuming application's decision. OPA and Casbin are natural complements.
Session binding. HWT tokens are bearer credentials. For deployments where token theft is a primary threat, DPoP (RFC 9449) is a compatible proof-of-possession extension — no HWT format changes required.
Air-gapped systems. HWT assumes network availability for key discovery. Expired tokens do not work offline. This is correct behavior.
The rationale: narrower protocol commitments produce more precise security claims and clearer integrator responsibility. When the boundary is explicit, the application knows exactly what it owns.
Algorithms · Codecs
| Algorithm | Notes |
|---|---|
| Ed25519 | Recommended default. Fast, small signatures. |
| ECDSA P-256 | Wide hardware and HSM support. |
| ECDSA P-384 | Higher security margin where required. |
| HMAC | Single-service only. Not conforming for cross-domain use. |
| Codec | Identifier | Notes |
|---|---|---|
| JSON | j |
Required baseline. All implementations. |
| JSON Extended | jx |
Dates, BigInt, typed arrays, Maps, Sets. Optional experimental import. |
| CBOR, MessagePack | community | Binary codecs. See CONVENTIONS.md. |
Status
Draft v0.7. Protocol structure stable as of 2026-04.
Open: IANA well-known URI registration · token exchange scope semantics
Get started
Install the JS library:
import Hwtr from 'jsr:@hwt/hwtr-js'
Working examples (Deno, Node, Cloudflare Workers): hwt-demo →
Read the specification:
Implementations in other languages:
Contribute:
Apache License 2.0 · Copyright 2026 Jim Montgomery and HWT Contributors · github.com/hwt-protocol