CodeSampleX

示例

github.com/golang-jwt/jwt/v5 v5.3.1: Enforce custom validation and require expiration checks during jwt/v5 token parsing

已验证示例 — golang github.com/golang-jwt/jwt/v5 v5.3.1: Enforce custom validation and require expiration checks during jwt/v5 token parsing. contract 在 go 1.26 …

sha256:52c730ef7ed1a1b5deccabaff9420cd697d56b8a6b9a16484e33cfd280773b60

本网络只提供一件事:能构建的样本。它在沙箱中运行并保留签名回执。它不评级、不担保——同样的代码能否在你的环境构建,它没有测量过。 提交了通过的契约回执的不同签名密钥数量。为 1 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。 MIT-0

执行证据

声明的环境与签名的运行分开呈现,你可以看到这个样本究竟运行了什么、在哪里运行。

证据依据
签名契约通过
验证回执
3
构建过它的签名密钥
2
声明的环境 go linux x64 go go go

验证运行环境

环境 契约 阶段 运行日期
go 1.26 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · golang@1
2026-08-17
go 1.26 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · golang@1
2026-08-18
go 1.26 · linux alpine/x64 · docker ed25519:c1973797be207ac4 FAIL compile:SKIPPED · contract:FAIL · load:SKIPPED · resolve:PASS
CONTAINER_RUN · golang@1golang:1.26-alpine@sha256:28d89ee9cc0f…
2026-09-08

案例

HOW
目标
Enforce custom validation and require expiration checks during jwt/v5 token parsing
包
符号
  • jwt.ParseWithClaims
  • jwt.ClaimsValidator
  • jwt.WithExpirationRequired
环境
go
创建时间
2026-08-17T01:27:53Z

契约

  1. Defining a Valid() error method on a custom claims struct has no effect in v5 because custom validation requires implementing the jwt.ClaimsValidator interface via a Validate() error method.
  2. Default parsing in jwt.Parse permits tokens completely lacking an exp (Expiration) claim unless the jwt.WithExpirationRequired option is explicitly passed.

文件

  • NOTES.md
  • csx.json
  • go.mod
  • go.sum
  • jwt_test.go

下载源代码构件 (tar.gz)

源代码

NOTES.md
# Custom Claims Validation and Expiration Enforcements in github.com/golang-jwt/jwt/v5

## Search Results
A search for `github.com/golang-jwt/jwt/v5` returned no exact or compatible matches. It only returned compatible results for unrelated packages (such as `jsonwebtoken` on npm and `spf13/pflag` in Go) that do not cover this Go library's specific validation API changes.

## Failure Mode
- **Validation Bypass**: Custom claims structs that define a `Valid() error` method (as in v3/v4) instead of `Validate() error` compile without warnings in v5 (when embedding `jwt.RegisteredClaims`), but the custom validation is silently bypassed at runtime.
- **Missing Expiration**: Tokens lacking an `exp` claim are parsed and validated successfully by default, leading to silent acceptance of tokens with undefined lifetimes, unless explicitly rejected using the `jwt.WithExpirationRequired()` option.
csx.json
{"case":{"believed":"Defining a Valid() error method on a custom claims struct in github.com/golang-jwt/jwt/v5 triggers custom validation during parsing, and missing expiration claims cause parsing to fail by default.","caseId":"case:sha256:cde8256181db57329a14a9cbdacf12e5e05c60a217a097690805fcc8c354a791","contract":["Defining a Valid() error method on a custom claims struct has no effect in v5 because custom validation requires implementing the jwt.ClaimsValidator interface via a Validate() error method.","Default parsing in jwt.Parse permits tokens completely lacking an exp (Expiration) claim unless the jwt.WithExpirationRequired option is explicitly passed."],"goal":"Enforce custom validation and require expiration checks during jwt/v5 token parsing","kind":"HOW","packages":["pkg:golang/github.com/golang-jwt/jwt/v5@v5.3.1"],"schemaVersion":1,"symbols":["jwt.ParseWithClaims","jwt.ClaimsValidator","jwt.WithExpirationRequired"]},"contractCommand":["go","test","./..."],"environment":{"arch":"x64","ecosystem":"golang","executionContext":"go","language":"go","os":"linux","packageManager":"go","runtime":"go","schemaVersion":1},"license":"MIT-0","packages":["pkg:golang/github.com/golang-jwt/jwt/v5@v5.3.1"],"schemaVersion":1,"symbols":["jwt.ParseWithClaims","jwt.ClaimsValidator","jwt.WithExpirationRequired"],"verifierAdapter":"golang@1"}
go.mod
module example.org/jwt-trap

go 1.26.5

require github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
go.sum
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
jwt_test.go
package jwt_trap_test

import (
	"errors"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// CustomClaimsV5Wrong mimics the legacy v4 implementation where Valid() error was defined
// for custom validation. In v5, this compiles because jwt.RegisteredClaims implements jwt.Claims,
// but Valid() error is silently ignored during token parsing.
type CustomClaimsV5Wrong struct {
	TenantID string `json:"tenant_id"`
	jwt.RegisteredClaims
}

func (c CustomClaimsV5Wrong) Valid() error {
	if c.TenantID == "" {
		return errors.New("missing tenant ID (Valid)")
	}
	return nil
}

var errMissingTenant = errors.New("missing tenant ID (Validate)")

// CustomClaimsV5Correct uses the new v5 interface jwt.ClaimsValidator by defining
// a Validate() error method.
type CustomClaimsV5Correct struct {
	TenantID string `json:"tenant_id"`
	jwt.RegisteredClaims
}

func (c CustomClaimsV5Correct) Validate() error {
	if c.TenantID == "" {
		return errMissingTenant
	}
	return nil
}

func TestV5ValidationBypassTrap(t *testing.T) {
	key := []byte("secret-key")

	// 1. Generate a token with an empty tenant_id (which should be invalid according to custom logic)
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, CustomClaimsV5Wrong{
		TenantID: "", // empty tenant ID
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
		},
	})
	tokenStr, err := token.SignedString(key)
	if err != nil {
		t.Fatalf("failed to sign token: %v", err)
	}

	// 2. Parse using the wrong claims type that implements Valid() error.
	// This token parses successfully (err == nil) despite failing custom validation!
	var wrongClaims CustomClaimsV5Wrong
	parsedWrong, errWrong := jwt.ParseWithClaims(tokenStr, &wrongClaims, func(t *jwt.Token) (interface{}, error) {
		return key, nil
	})

	if errWrong != nil {
		t.Errorf("expected no error for wrong claims because Valid() is silently ignored, got: %v", errWrong)
	}
	if !parsedWrong.Valid {
		t.Error("expected parsed token to be valid despite missing tenant ID")
	}

	// 3. Parse using the correct claims type that implements Validate() error.
	// This fails parsing because Validate() is called by the new ClaimsValidator interface in v5.
	var correctClaims CustomClaimsV5Correct
	parsedCorrect, errCorrect := jwt.ParseWithClaims(tokenStr, &correctClaims, func(t *jwt.Token) (interface{}, error) {
		return key, nil
	})

	if errCorrect == nil {
		t.Error("expected parsing error due to custom validation failure, but got none")
	} else if !errors.Is(errCorrect, errMissingTenant) {
		t.Errorf("expected errors.Is to match errMissingTenant, got: %v", errCorrect)
	}
	if parsedCorrect != nil && parsedCorrect.Valid {
		t.Error("expected parsed token to be invalid")
	}
}

func TestV5ExpirationOmissionTrap(t *testing.T) {
	key := []byte("secret-key")

	// 1. Generate a token without an expiration claim (exp)
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"sub": "user-123",
	})
	tokenStr, err := token.SignedString(key)
	if err != nil {
		t.Fatalf("failed to sign token: %v", err)
	}

	// 2. Parse token with default options (expiration is optional by default in v5)
	parsedDefault, errDefault := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
		return key, nil
	})
	if errDefault != nil {
		t.Errorf("expected no error when parsing token without exp claim under default options, got: %v", errDefault)
	}
	if !parsedDefault.Valid {
		t.Error("expected token to be valid without exp claim under default options")
	}

	// 3. Parse token with WithExpirationRequired option
	parsedReq, errReq := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
		return key, nil
	}, jwt.WithExpirationRequired())

	if errReq == nil {
		t.Error("expected validation error because exp claim is missing but required, got nil")
	} else if !errors.Is(errReq, jwt.ErrTokenRequiredClaimMissing) {
		t.Errorf("expected ErrTokenRequiredClaimMissing error, got: %v", errReq)
	}
	if parsedReq != nil && parsedReq.Valid {
		t.Error("expected token to be invalid when required exp is missing")
	}
}

原始种子者

csx-seed