Documentation
¶
Overview ¶
Package jwt signs and verifies JSON Web Tokens (HS256) isomorphically: the same code runs on the native backend and inside a WASM/edge binary.
The library is deliberately small and closed: HS256 only, one claim set, no algorithm negotiation. See docs/ARCHITECTURE.md for why.
Index ¶
Constants ¶
const DefaultTTL = 86400 // 24h, in seconds
DefaultTTL is the lifetime NewClaims uses when ttl <= 0.
Variables ¶
var ( // ErrEmptySecret is a refusal, not a failure. HMAC over an empty key is valid math: // it produces a token that verifies. A zero-value config would therefore mint // tokens that ANYONE can forge, and nothing would ever look wrong. // // It is an `error`, not an Outcome, because it means THE CALLER is broken — not the // token. The two must never share a channel. ErrEmptySecret = fmt.Err("jwt", "secret", "empty") // ErrEmptySubject: a token that authenticates nobody is never what the caller meant. ErrEmptySubject = fmt.Err("jwt", "subject", "empty") )
Functions ¶
func Sign ¶ added in v0.0.2
Sign returns a signed HS256 token. It refuses to mint a forgeable or meaningless token rather than handing back one that merely looks fine.
func Verify ¶ added in v0.0.2
Verify authenticates a token and returns its verdict.
The two return channels mean different things, and that separation IS the API:
error — THE CALLER is broken (an empty secret). A configuration bug. Outcome — what the TOKEN is: Valid, Expired, or Forged. Never an error.
Claims are meaningful only when the Outcome is Valid; otherwise they are zero.
The `alg` field of the header is READ BY NOBODY, and that is the point: this verifier always recomputes HS256. Choosing the algorithm from a value carried inside the untrusted token is the classic alg-confusion vulnerability — it is how `{"alg":"none"}` forgeries get accepted. Do not "fix" this by parsing the header.
Types ¶
type Claims ¶ added in v0.0.2
type Claims struct {
Sub string // subject: who the token authenticates
Exp int64 // expiry, unix seconds
Iat int64 // issued at, unix seconds
}
Claims is the payload. Closed on purpose: the registered claims this ecosystem actually uses. No `map[string]any` bag — that is how JWT libraries grow holes.
func (*Claims) DecodeFields ¶ added in v0.0.2
func (c *Claims) DecodeFields(r model.FieldReader)
func (Claims) EncodeFields ¶ added in v0.0.2
func (c Claims) EncodeFields(w model.FieldWriter)
type Outcome ¶ added in v0.0.3
type Outcome uint8
Outcome is the CLOSED set of verdicts on a token. It is not an error: a token being expired or forged is this function working correctly, and the caller must act differently on each — "log in again" is not "you are under attack".
It is an enum rather than a sentinel error on purpose. With `(Claims, error)` a caller can write `if err != nil { alarm() }` and collapse a routine expiry into a forgery alarm — which is exactly what happened in tinywasm/user, drowning real tampering in noise. A closed type makes that collapse something you have to deliberately write, not something you get by forgetting.
const ( // Forged is the ZERO VALUE: closed by default. Anything not proven authentic — // wrong shape, bad signature, undecodable payload, missing claims — is this. // The verdict does not say WHICH: telling "bad signature" apart from "bad base64" // tells an attacker where they stand. Forged Outcome = iota // Valid: authentic and in date. The Claims returned alongside are trustworthy. Valid // Expired: authentic, but past its `exp`. NOT an attack — the session simply ended. Expired )