jwt

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: MIT Imports: 8 Imported by: 0

README

Go Reference License Stay with Ukraine

jwt

jwt issues and verifies compact JSON Web Tokens, deliberately limited to HS256. It follows the JWS compact serialization (RFC 7515) and the registered claims (RFC 7519), but supports exactly one algorithm with strict defaults - a smaller surface is a safer surface. Zero dependencies, standard library only.

Install

go get github.com/goloop/jwt

Sign

token, err := jwt.Sign(jwt.Claims{
	Subject:   "user-123",
	Issuer:    "api",
	Audience:  jwt.Audience{"web"},
	ExpiresAt: time.Now().Add(time.Hour).Unix(),
	IssuedAt:  time.Now().Unix(),
	Extra:     map[string]any{"role": "admin"},
}, key)

The header is always {"alg":"HS256","typ":"JWT"}. Custom claims go in Claims.Extra.

Verify

claims, err := jwt.Verify(token, key,
	jwt.WithIssuer("api"),
	jwt.WithAudience("web"),
	jwt.WithLeeway(30*time.Second),
)

Verify:

  • requires alg=HS256 (rejects none, RS256, everything else);
  • requires a present exp;
  • verifies the signature in constant time before interpreting the payload;
  • checks exp/nbf/iat with optional leeway, and issuer/audience when set.

Key rotation

claims, err := jwt.Verify(token, newKey, jwt.WithKey(oldKey))

Tokens are signed with the primary key; verification accepts any configured key.

Not supported (by design)

RS/ES/PS algorithms, none, JWE encryption, JWKS, and parsing without signature verification.

Documentation

License

MIT - see LICENSE.

Documentation

Overview

Package jwt issues and verifies compact JSON Web Tokens, deliberately limited to HS256, standard library only.

It follows the JWS compact serialization of RFC 7515 and the registered claims of RFC 7519, but supports exactly one algorithm (HMAC-SHA256) with strict defaults. There is no algorithm negotiation, no "none", and no asymmetric keys: a smaller surface is a safer surface.

Signing

claims := jwt.Claims{
    Subject:   "user-123",
    Issuer:    "api",
    ExpiresAt: time.Now().Add(time.Hour).Unix(),
    IssuedAt:  time.Now().Unix(),
}
token, err := jwt.Sign(claims, key)

The header is always {"alg":"HS256","typ":"JWT"}. Custom claims go in Claims.Extra; a registered claim name there is rejected (ErrReservedClaim), so registered claims come only from the typed fields. The key must be at least 32 bytes (ErrWeakKey), the HMAC-SHA256 output size required by RFC 7518.

Verifying

claims, err := jwt.Verify(token, key,
    jwt.WithIssuer("api"),
    jwt.WithLeeway(30*time.Second),
)

Verify requires alg=HS256 and a present exp, verifies the HMAC signature (compared with hmac.Equal) before interpreting the payload, and then checks exp/nbf/iat plus any configured issuer and audience. It rejects a token that declares a crit header, a segment that is not strict base64url, and a registered claim of the wrong JSON type. For key rotation, pass additional keys with WithKey: tokens are signed with the primary key and verified against any configured key. WithMaxBytes bounds the work spent on untrusted input.

Not supported

RS/ES/PS algorithms, "none", JWE encryption and JWKS are out of scope by design. Parsing without signature verification is not exposed.

See DOC.md (English) and DOC.UK.md (Ukrainian) for the full reference.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoKey is returned when signing or verifying without a key.
	ErrNoKey = errors.New("jwt: no signing key")

	// ErrWeakKey is returned when the HS256 key is shorter than 32 bytes. RFC
	// 7518 requires a key at least as long as the hash output (256 bits) for
	// HMAC-SHA256, so shorter keys are rejected instead of silently weakening
	// every token.
	ErrWeakKey = errors.New("jwt: key too short, HS256 needs at least 32 bytes")

	// ErrMalformed is returned when the token is not a well-formed
	// header.payload.signature triple, a segment is not strict base64url, or a
	// registered claim has the wrong JSON type.
	ErrMalformed = errors.New("jwt: malformed token")

	// ErrUnsupportedCritical is returned when the header carries a crit
	// parameter: this verifier implements no extensions, so it must reject any
	// token that declares one as mandatory (RFC 7515 section 4.1.11).
	ErrUnsupportedCritical = errors.New("jwt: unsupported crit header parameter")

	// ErrReservedClaim is returned by Sign when Claims.Extra contains a
	// registered claim name (iss, sub, aud, exp, nbf, iat, jti). Set those
	// through the typed fields so the source of each registered claim is
	// unambiguous.
	ErrReservedClaim = errors.New("jwt: Extra must not contain registered claim names")

	// ErrTooLarge is returned by Verify when the token exceeds the configured
	// maximum size (see WithMaxBytes).
	ErrTooLarge = errors.New("jwt: token exceeds maximum size")

	// ErrAlgMismatch is returned when the token header does not use HS256
	// (this includes "none" and any asymmetric algorithm).
	ErrAlgMismatch = errors.New("jwt: unexpected algorithm, want HS256")

	// ErrSignature is returned when the signature does not verify against any
	// configured key.
	ErrSignature = errors.New("jwt: signature mismatch")

	// ErrMissingExpiry is returned when the token has no exp claim, which is
	// required.
	ErrMissingExpiry = errors.New("jwt: missing exp claim")

	// ErrExpired is returned when the token is past its exp (plus leeway).
	ErrExpired = errors.New("jwt: token expired")

	// ErrNotYetValid is returned when the token is before its nbf (minus leeway).
	ErrNotYetValid = errors.New("jwt: token not yet valid")

	// ErrIssuedInFuture is returned when the token's iat is in the future
	// (minus leeway).
	ErrIssuedInFuture = errors.New("jwt: token issued in the future")

	// ErrIssuer is returned when the iss claim does not match the expected one.
	ErrIssuer = errors.New("jwt: issuer mismatch")

	// ErrAudience is returned when the aud claim does not include the expected
	// audience.
	ErrAudience = errors.New("jwt: audience mismatch")
)

Functions

func Sign

func Sign(claims Claims, key []byte) (string, error)

Sign creates a signed HS256 JWT for the claims. The key must be at least 32 bytes. Claims.Extra must not carry registered claim names; set those through the typed fields.

Example
package main

import (
	"fmt"
	"time"

	"github.com/goloop/jwt"
)

func main() {
	key := []byte("0123456789abcdef0123456789abcdef")

	token, err := jwt.Sign(jwt.Claims{
		Subject:   "user-123",
		Issuer:    "api",
		Audience:  jwt.Audience{"web"},
		ExpiresAt: time.Now().Add(time.Hour).Unix(),
		IssuedAt:  time.Now().Unix(),
		Extra:     map[string]any{"role": "admin"},
	}, key)
	if err != nil {
		panic(err)
	}

	claims, err := jwt.Verify(token, key,
		jwt.WithIssuer("api"),
		jwt.WithAudience("web"),
		jwt.WithLeeway(30*time.Second),
	)
	if err != nil {
		panic(err)
	}

	fmt.Println(claims.Subject, claims.Extra["role"])
}
Output:
user-123 admin

Types

type Audience

type Audience []string

Audience is the aud claim. Per RFC 7519 it may be a single string or an array of strings; it marshals to a string when it holds one value.

func (Audience) Contains

func (a Audience) Contains(v string) bool

Contains reports whether the audience includes v.

func (Audience) MarshalJSON

func (a Audience) MarshalJSON() ([]byte, error)

MarshalJSON encodes a single-element audience as a string, otherwise as an array.

func (*Audience) UnmarshalJSON

func (a *Audience) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts either a string or an array of strings.

type Claims

type Claims struct {
	Issuer    string
	Subject   string
	Audience  Audience
	ExpiresAt int64
	NotBefore int64
	IssuedAt  int64
	ID        string
	Extra     map[string]any
}

Claims holds the RFC 7519 registered claims plus any custom claims in Extra. Times are Unix seconds (the RFC NumericDate representation).

func Verify

func Verify(token string, key []byte, opts ...Option) (Claims, error)

Verify parses and validates an HS256 JWT and returns its claims. It requires alg=HS256 and a present exp; it verifies the HMAC signature (compared with hmac.Equal) before interpreting the payload, then checks exp/nbf/iat plus any configured issuer and audience. A token that declares a crit header is rejected, since this verifier implements no extensions.

func (Claims) MarshalJSON

func (c Claims) MarshalJSON() ([]byte, error)

MarshalJSON merges the registered claims and Extra into one JSON object. Extra carries only custom claims; a registered claim name in Extra is an error (ErrReservedClaim), because the typed fields are the single source of truth for those claims.

func (*Claims) UnmarshalJSON

func (c *Claims) UnmarshalJSON(data []byte) error

UnmarshalJSON extracts the registered claims and keeps the rest in Extra. A registered claim with the wrong JSON type is rejected (ErrMalformed) rather than silently coerced, so a malformed exp or aud cannot slip through verification as if it were absent.

type Option

type Option func(*options)

Option configures Verify.

func WithAudience

func WithAudience(audience string) Option

WithAudience requires the aud claim to include audience.

func WithClock

func WithClock(now func() time.Time) Option

WithClock overrides the time source (for testing). A nil function is ignored, leaving the default of time.Now.

func WithIssuer

func WithIssuer(issuer string) Option

WithIssuer requires the iss claim to equal issuer.

func WithKey

func WithKey(key []byte) Option

WithKey adds another key to try during verification, for key rotation. Tokens are always signed with the primary key; verification accepts any configured key. A key shorter than 32 bytes is ignored, matching the signing minimum.

func WithLeeway

func WithLeeway(d time.Duration) Option

WithLeeway allows a clock-skew tolerance when checking exp, nbf and iat.

func WithMaxBytes

func WithMaxBytes(n int) Option

WithMaxBytes rejects a token longer than n bytes before any parsing, to bound the work spent on untrusted input. The default (0) imposes no limit; set a limit when verifying tokens from the open internet.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL