Skip to content

v0.3.0

Latest

Choose a tag to compare

@kataras kataras released this 21 Aug 13:15
· 1 commit to main since this release
c736628

jwt v0.3.0

The Go 1.27 release. Every change below follows from
that toolchain bump, either because
Go 1.27 is now required to build the package at all,
or because it made something the
package already did possible to do faster or more
directly. Nothing about what verifies or
what gets signed changed on purpose:
testdata/golden.json is byte-for-byte identical to
v0.2.0.

Upgrading

Two things to check before you take it:

  1. Bump your own go directive to 1.27 or newer
    first.
    go.mod now requires it, for
    encoding/json/v2 and the standard library uuid
    package. A consumer on an older
    toolchain gets a build error, not a warning.
  2. If anything parses or length-checks SignPair's
    generated jti,
    expect 36
    characters (a UUID) instead of 22 (base64url). Set
    PairOptions.GenerateID to keep the
    old format if you need to; the doc comment on the
    field shows the four-line replacement.
    A token already signed under the old format still
    verifies, since jti is opaque to
    everything but whoever reads it.

Nothing else needs a code change. The
Marshal/Unmarshal and Claims/Merge behavior a
caller can observe from outside the package is
unchanged, and the two new generic methods
are additive.

Faster, from the same two changes

Verify's allocation count dropped for two
independent reasons: token parsing moved from
bytes.Split to bytes.CutLast/bytes.Cut (no more
[][3]byte allocation, no re-join of
header and payload for signature verification), and
the default Marshal/Unmarshal moved
to encoding/json/v2, with an option set chosen to
reproduce encoding/json's (v1) output
exactly rather than to change it.

Before (Go 1.26.5) Now (Go 1.27.0)
Verify 18 allocs, 1376 B 12 allocs, 968 B
Sign (map) 23 allocs, 1441 B 27 allocs, 1441 B
Sign (struct) 21 allocs, 1520 B 23 allocs,1520 B

Sign's allocation count moved the other way, and
that one is not this package's doing:
Go 1.27's own encoding/json is now implemented in
terms of encoding/json/v2 with a
compatibility layer, and that layer costs more for a
map- or struct-shaped marshal than Go
1.26's implementation did. Moving Marshal's own call
site directly onto
encoding/json/v2 recovered the speed but not the
allocation count. All figures measured
windows/arm64; see CHANGELOG.md and
book/14-performance.md for the fuller breakdown.

Fixed

An EC JWK is parsed with
ecdsa.ParseUncompressedPublicKey rather than by
assigning X
and Y.
Go 1.26 deprecated both fields, so the old
code was a lint failure under this
toolchain, and the replacement does more than silence
the warning: crypto/ecdsa rejects a
point that is not on the curve. A malformed or hostile
JWKS entry that used to become an
*ecdsa.PublicKey and fail later, unpredictably, now
fails at the point it is read.

New

  • (*VerifiedToken).ClaimsAs[T] and
    (*UnverifiedToken).ClaimsAs[T],
    the generic form
    of Claims(dest any): user, err := verifiedToken.ClaimsAs[UserClaims]() in place of
    declaring a variable and passing its address.
  • VerifyAs[T], combining Verify and
    ClaimsAs[T] for the common case where nothing
    else about the *VerifiedToken is needed: claims, verifiedToken, err := jwt.VerifyAs[UserClaims](alg, key, token).
  • SignPair's default jti is a version 7 UUID
    from the standard library, time-ordered
    and friendlier to a blocklist or any other index
    keyed on jti, in place of 22 characters
    of base64url randomness. See Upgrading above.

Also in this release

  • go fix's Go 1.27 modernizers, applied and reviewed: errors.AsType[E] in place of the
    var target E; errors.As(err, &target) pattern, sync.WaitGroup.Go in place of manual
    Add/Done/go func(), range-over-integer in place of counted loops. None change
    behavior.
  • CI moved to Go 1.27, actions/setup-go/actions/checkout to v7 (Node 24, off the
    deprecated Node 20 both v6 still ran on), and the pinned golangci-lint version to
    v2.13. The last one is not routine: v2.12's release binary was built with Go 1.26.2, and
    a golangci-lint binary refuses to run at all once a module's go directive exceeds the
    Go version it was itself built with. go.mod moving to go 1.27 made that pin a hard
    CI failure, not a lint diff.
  • go.mod still has no requirements, and it is not going to get any.

Full detail, including everything not listed here, is in CHANGELOG.md.