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. go 1.26 · linux…

sha256:52c730ef7ed1a1b5deccabaff9420cd697d56b8a6b9a16484e33cfd280773b60

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. 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