From 0c3a40e0a0767d85e22b5272fefd20b17755613c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frederik=20L=C3=B6ffert?= Date: Fri, 13 Feb 2026 10:36:49 +0100 Subject: [PATCH 1/2] feat: migrate to latest golang-jwt/jwt/v5 --- examples/go.mod | 2 +- examples/go.sum | 4 +- examples/middlewares/jwtfromcookie/main.go | 16 +++--- examples/middlewares/jwtfromtoken/main.go | 10 ++-- go.mod | 2 +- go.sum | 4 +- jwt/jwt.go | 2 +- jwt/jwtclaims.go | 58 ++++++++++++++++++---- jwt/jwtkey.go | 2 +- jwt/keyfunc.go | 4 +- net/http/middleware/jwt.go | 22 ++++---- 11 files changed, 82 insertions(+), 44 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 353f5754..01bd3fbb 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -17,7 +17,7 @@ require ( github.com/foomo/keel/net/stream v0.0.0-00010101000000-000000000000 github.com/foomo/keel/persistence/mongo v0.0.0-00010101000000-000000000000 github.com/foomo/keel/persistence/postgres v0.0.0-00010101000000-000000000000 - github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 github.com/nats-io/nats.go v1.47.0 github.com/pkg/errors v0.9.1 diff --git a/examples/go.sum b/examples/go.sum index 43fd3411..d5cc3e49 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -113,8 +113,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +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= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/examples/middlewares/jwtfromcookie/main.go b/examples/middlewares/jwtfromcookie/main.go index 2443648d..56bf9de4 100644 --- a/examples/middlewares/jwtfromcookie/main.go +++ b/examples/middlewares/jwtfromcookie/main.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/foomo/keel/service" - jwt2 "github.com/golang-jwt/jwt" + gojwt "github.com/golang-jwt/jwt/v5" "go.uber.org/zap" "github.com/foomo/keel" @@ -19,7 +19,7 @@ import ( ) type CustomClaims struct { - jwt2.StandardClaims + gojwt.RegisteredClaims Name string `json:"name"` Language string `json:"language"` } @@ -84,11 +84,11 @@ func main() { // use custom token provider middleware.JWTWithTokenProvider(tokenProvider), // user custom claims - middleware.JWTWithClaimsProvider(func() jwt2.Claims { + middleware.JWTWithClaimsProvider(func() gojwt.Claims { return &CustomClaims{} }), // handle existing claim - middleware.JWTWithClaimsHandler(func(l *zap.Logger, w http.ResponseWriter, r *http.Request, claims jwt2.Claims) bool { + middleware.JWTWithClaimsHandler(func(l *zap.Logger, w http.ResponseWriter, r *http.Request, claims gojwt.Claims) bool { if value, ok := claims.(*CustomClaims); ok { var language string switch { @@ -121,11 +121,11 @@ func main() { } }), // create cookie if missing - middleware.JWTWithMissingTokenHandler(func(l *zap.Logger, w http.ResponseWriter, r *http.Request) (jwt2.Claims, bool) { + middleware.JWTWithMissingTokenHandler(func(l *zap.Logger, w http.ResponseWriter, r *http.Request) (gojwt.Claims, bool) { claims := &CustomClaims{ - StandardClaims: jwt.NewStandardClaims(), - Name: "JWT From Cookie Example", - Language: "de", + RegisteredClaims: jwt.NewStandardClaims(), + Name: "JWT From Cookie Example", + Language: "de", } if token, err := jwtInst.GetSignedToken(claims); err != nil { httputils.InternalServerError(l, w, r, err) diff --git a/examples/middlewares/jwtfromtoken/main.go b/examples/middlewares/jwtfromtoken/main.go index 915e5301..a2cf82fd 100644 --- a/examples/middlewares/jwtfromtoken/main.go +++ b/examples/middlewares/jwtfromtoken/main.go @@ -6,7 +6,7 @@ import ( "net/http" "github.com/foomo/keel/service" - jwt2 "github.com/golang-jwt/jwt" + gojwt "github.com/golang-jwt/jwt/v5" "github.com/foomo/keel" "github.com/foomo/keel/jwt" @@ -24,7 +24,7 @@ func main() { contextKey := "custom" type CustomClaims struct { - jwt2.StandardClaims + gojwt.RegisteredClaims Name string `json:"name"` } @@ -56,8 +56,8 @@ func main() { }) svs.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { if token, err := jwtInst.GetSignedToken(CustomClaims{ - StandardClaims: jwt.NewStandardClaims(), - Name: "JWT From Token Example", + RegisteredClaims: jwt.NewRegisteredClaims(jwt.WithOffset(jwt.MaxTimeDifferenceBetweenNodes)), + Name: "JWT From Token Example", }); err != nil { httputils.InternalServerError(l, w, r, err) } else { @@ -77,7 +77,7 @@ func main() { // use custom token provider middleware.JWTWithTokenProvider(tokenProvider), // user custom claims - middleware.JWTWithClaimsProvider(func() jwt2.Claims { + middleware.JWTWithClaimsProvider(func() gojwt.Claims { return &CustomClaims{} }), ), diff --git a/go.mod b/go.mod index f6937d52..6c52807d 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/fbiville/markdown-table-formatter v0.3.0 github.com/foomo/gostandards v0.2.0 github.com/go-logr/logr v1.4.3 - github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 github.com/grafana/otel-profiling-go v0.5.1 github.com/grafana/pyroscope-go v1.2.7 diff --git a/go.sum b/go.sum index ec739c1e..d9a7f061 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +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= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/jwt/jwt.go b/jwt/jwt.go index 7d41cf9d..c4f471ea 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -1,7 +1,7 @@ package jwt import ( - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" ) type ( diff --git a/jwt/jwtclaims.go b/jwt/jwtclaims.go index 0ce2c495..8977ecf0 100644 --- a/jwt/jwtclaims.go +++ b/jwt/jwtclaims.go @@ -3,25 +3,63 @@ package jwt import ( "time" - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" ) // MaxTimeDifferenceBetweenNodes represents an offset that should be taken // into account when creating e.g. jwt tokens with the `notBefore` flag. -var MaxTimeDifferenceBetweenNodes = time.Second * 30 +var MaxTimeDifferenceBetweenNodes = 30 * time.Second -func NewStandardClaims() jwt.StandardClaims { - now := time.Now().Add(-MaxTimeDifferenceBetweenNodes) +// Deprecated: NewStandardClaims use NewRegisteredClaims instead. +func NewStandardClaims() jwt.RegisteredClaims { + return NewRegisteredClaims( + WithOffset(MaxTimeDifferenceBetweenNodes), + ) +} + +// Deprecated: NewStandardClaimsWithLifetime use NewRegisteredClaimsWithLifetime instead. +func NewStandardClaimsWithLifetime(lifetime time.Duration) jwt.RegisteredClaims { + return NewRegisteredClaimsWithLifetime(lifetime, WithOffset(MaxTimeDifferenceBetweenNodes)) +} + +// RegisteredClaimsOption configures how RegisteredClaims are created. +type RegisteredClaimsOption func(*registeredClaimsOptions) + +type registeredClaimsOptions struct { + offset time.Duration +} + +// WithOffset sets the offset to account for time differences between nodes. +func WithOffset(offset time.Duration) RegisteredClaimsOption { + return func(o *registeredClaimsOptions) { + o.offset = offset + } +} + +// NewRegisteredClaims returns a new jwt.RegisteredClaims with the IssuedAt and NotBefore fields set to the current time plus the given offset. +// The offset can be used to account for time differences between nodes in a distributed system. +// If no offset option is provided, MaxTimeDifferenceBetweenNodes is used as the default. +func NewRegisteredClaims(opts ...RegisteredClaimsOption) jwt.RegisteredClaims { + o := ®isteredClaimsOptions{offset: MaxTimeDifferenceBetweenNodes} + for _, opt := range opts { + opt(o) + } + // set IssuedAt and NotBefore to the current time minus the offset to account for time differences between nodes + now := time.Now() + if o.offset.Milliseconds() > 0 { + now = now.Add(o.offset * -1) + } - return jwt.StandardClaims{ - IssuedAt: now.Unix(), - NotBefore: now.Unix(), + return jwt.RegisteredClaims{ + IssuedAt: jwt.NewNumericDate(now), + NotBefore: jwt.NewNumericDate(now), } } -func NewStandardClaimsWithLifetime(lifetime time.Duration) jwt.StandardClaims { - claims := NewStandardClaims() - claims.ExpiresAt = claims.IssuedAt + int64(lifetime.Seconds()) +// NewRegisteredClaimsWithLifetime returns a new jwt.RegisteredClaims with the IssuedAt and NotBefore fields set to the current time plus the given optional offset and the ExpiresAt field set to the current time plus the given lifetime. +func NewRegisteredClaimsWithLifetime(lifetime time.Duration, opts ...RegisteredClaimsOption) jwt.RegisteredClaims { + claims := NewRegisteredClaims(opts...) + claims.ExpiresAt = jwt.NewNumericDate(claims.IssuedAt.Add(lifetime)) return claims } diff --git a/jwt/jwtkey.go b/jwt/jwtkey.go index 07d07621..e363c88b 100644 --- a/jwt/jwtkey.go +++ b/jwt/jwtkey.go @@ -8,7 +8,7 @@ import ( "os" "strings" - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" "github.com/pkg/errors" ) diff --git a/jwt/keyfunc.go b/jwt/keyfunc.go index 53f395f9..ef2f0683 100644 --- a/jwt/keyfunc.go +++ b/jwt/keyfunc.go @@ -1,12 +1,12 @@ package jwt import ( - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" "github.com/pkg/errors" ) func DefaultKeyFunc(key Key, deprecatedKeys map[string]Key) jwt.Keyfunc { - return func(token *jwt.Token) (interface{}, error) { + return func(token *jwt.Token) (any, error) { if token.Method.Alg() != jwt.SigningMethodRS256.Name { return nil, errors.New("unexpected jwt signing method: " + token.Method.Alg()) } diff --git a/net/http/middleware/jwt.go b/net/http/middleware/jwt.go index a400f3bd..2715a60e 100644 --- a/net/http/middleware/jwt.go +++ b/net/http/middleware/jwt.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - jwt2 "github.com/golang-jwt/jwt" + gojwt "github.com/golang-jwt/jwt/v5" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" @@ -24,11 +24,11 @@ type ( ErrorHandler JWTErrorHandler } JWTOption func(*JWTOptions) - JWTClaimsProvider func() jwt2.Claims - JWTClaimsHandler func(*zap.Logger, http.ResponseWriter, *http.Request, jwt2.Claims) bool + JWTClaimsProvider func() gojwt.Claims + JWTClaimsHandler func(*zap.Logger, http.ResponseWriter, *http.Request, gojwt.Claims) bool JWTErrorHandler func(*zap.Logger, http.ResponseWriter, *http.Request, error) bool - JWTMissingTokenHandler func(*zap.Logger, http.ResponseWriter, *http.Request) (jwt2.Claims, bool) - JWTInvalidTokenHandler func(*zap.Logger, http.ResponseWriter, *http.Request, *jwt2.Token) bool + JWTMissingTokenHandler func(*zap.Logger, http.ResponseWriter, *http.Request) (gojwt.Claims, bool) + JWTInvalidTokenHandler func(*zap.Logger, http.ResponseWriter, *http.Request, *gojwt.Token) bool ) // DefaultJWTErrorHandler function @@ -38,29 +38,29 @@ func DefaultJWTErrorHandler(l *zap.Logger, w http.ResponseWriter, r *http.Reques } // DefaultJWTMissingTokenHandler function -func DefaultJWTMissingTokenHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request) (jwt2.Claims, bool) { +func DefaultJWTMissingTokenHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request) (gojwt.Claims, bool) { return nil, true } // RequiredJWTMissingTokenHandler function -func RequiredJWTMissingTokenHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request) (jwt2.Claims, bool) { +func RequiredJWTMissingTokenHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request) (gojwt.Claims, bool) { httputils.BadRequestServerError(l, w, r, errors.New("missing jwt token")) return nil, false } // DefaultJWTInvalidTokenHandler function -func DefaultJWTInvalidTokenHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request, token *jwt2.Token) bool { +func DefaultJWTInvalidTokenHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request, token *gojwt.Token) bool { httputils.BadRequestServerError(l, w, r, errors.New("invalid jwt token")) return false } // DefaultJWTClaimsProvider function -func DefaultJWTClaimsProvider() jwt2.Claims { - return &jwt2.StandardClaims{} +func DefaultJWTClaimsProvider() gojwt.Claims { + return &gojwt.RegisteredClaims{} } // DefaultJWTClaimsHandler function -func DefaultJWTClaimsHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request, claims jwt2.Claims) bool { +func DefaultJWTClaimsHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request, claims gojwt.Claims) bool { return true } From 4713020d391f1fbf88a69b6d6af69411ec997523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frederik=20L=C3=B6ffert?= Date: Fri, 13 Feb 2026 10:48:17 +0100 Subject: [PATCH 2/2] fix: dependencies --- integration/gotsrpc/go.mod | 2 +- integration/gotsrpc/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/integration/gotsrpc/go.mod b/integration/gotsrpc/go.mod index 3ae80755..7e33ba16 100644 --- a/integration/gotsrpc/go.mod +++ b/integration/gotsrpc/go.mod @@ -24,7 +24,7 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grafana/pyroscope-go v1.2.7 // indirect diff --git a/integration/gotsrpc/go.sum b/integration/gotsrpc/go.sum index 2466bdad..76921649 100644 --- a/integration/gotsrpc/go.sum +++ b/integration/gotsrpc/go.sum @@ -61,8 +61,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +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= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=