go-kit/kit · error · ErrTokenInvalid

JWT was invalid

Error message

JWT was invalid

What it means

Returned by jwt.NewParser when jwt.ParseWithClaims succeeded without error but token.Valid is still false. It is the generic 'validation failed' outcome: the token was syntactically parseable and not expired/malformed/not-yet-active, but the signature or claims check rejected it without a more specific ValidationError bit being set.

Source

Thrown at auth/jwt/middleware.go:33

	JWTContextKey contextKey = "JWTToken"

	// JWTTokenContextKey is an alias for JWTContextKey.
	//
	// Deprecated: prefer JWTContextKey.
	JWTTokenContextKey = JWTContextKey

	// JWTClaimsContextKey holds the key used to store the JWT Claims in the
	// context.
	JWTClaimsContextKey contextKey = "JWTClaims"
)

var (
	// ErrTokenContextMissing denotes a token was not passed into the parsing
	// middleware's context.
	ErrTokenContextMissing = errors.New("token up for parsing was not passed through the context")

	// ErrTokenInvalid denotes a token was not able to be validated.
	ErrTokenInvalid = errors.New("JWT was invalid")

	// ErrTokenExpired denotes a token's expire header (exp) has since passed.
	ErrTokenExpired = errors.New("JWT is expired")

	// ErrTokenMalformed denotes a token was not formatted as a JWT.
	ErrTokenMalformed = errors.New("JWT is malformed")

	// ErrTokenNotActive denotes a token's not before header (nbf) is in the
	// future.
	ErrTokenNotActive = errors.New("token is not valid yet")

	// ErrUnexpectedSigningMethod denotes a token was signed with an unexpected
	// signing method.
	ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
)

// NewSigner creates a new JWT generating middleware, specifying key ID,
// signing string, signing method and the claims you would like it to contain.

View on GitHub (pinned to 78fbbceece)

Solutions

  1. Confirm the keyfunc returns exactly the key the signer used — log the kid header and compare
  2. Check issuer/audience/subject claim values against what the identity provider actually emits
  3. Verify both sides use the same signing method in NewSigner and NewParser
  4. Print the underlying jwt.ValidationError bits in a debug build to see which check failed before go-kit collapses it to ErrTokenInvalid

Example fix

// before: keyfunc ignores kid and always returns the current key
kf := func(*jwt.Token) (interface{}, error) { return []byte("new-secret"), nil }

// after: resolve the key by kid so old and new tokens both verify
var keys = map[string][]byte{"k1": []byte("old-secret"), "k2": []byte("new-secret")}
kf := func(t *jwt.Token) (interface{}, error) {
	kid, _ := t.Header["kid"].(string)
	if k, ok := keys[kid]; ok {
		return k, nil
	}
	return nil, errors.New("unknown kid")
}
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

resp, err := ep(ctx, req)
if err != nil {
	switch {
	case errors.Is(err, jwt.ErrTokenInvalid):
		// 401: signature/claims failed — force re-authentication, do not blind-retry
	case errors.Is(err, jwt.ErrTokenExpired):
		// 401: refresh token then retry once
	}
}

Prevention

When it happens

Trigger: The keyfunc returned a different key than the one used to sign (wrong secret, wrong public key, wrong kid resolution); claims-based validation (issuer, subject, audience via the v4 validator) failed without the malformed/expired/nbf bits; parser and signer disagree on signing method family so the signature check fails; token was tampered with so the signature simply doesn't verify.

Common situations: Rotating secrets where old tokens are verified with the new key; multi-environment setups sharing tokens across envs with different keys; keyfunc that ignores the kid header and always returns one key; subtle claim mismatches (iss URL with/without trailing slash) after an identity-provider change.

Related errors


AI-assisted analysis of go-kit/kit@78fbbceece (2026-08-15). Data as JSON: /api/errors/31382d40f64ace6e. Report an issue: GitHub.