diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b62c490..683f221 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: strategy: matrix: - go-version: [1.20.x, 1.21.x, 1.22.x] + go-version: [1.22.x, 1.23.x, 1.24.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} diff --git a/_example/main.go b/_example/main.go index a6d7559..bcd81f8 100644 --- a/_example/main.go +++ b/_example/main.go @@ -65,7 +65,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" - "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/lestrrat-go/jwx/v3/jwt" ) var tokenAuth *jwtauth.JWTAuth diff --git a/go.mod b/go.mod index 05a13ab..0249290 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,17 @@ toolchain go1.24.2 require ( github.com/go-chi/chi/v5 v5.2.1 - github.com/lestrrat-go/jwx/v2 v2.1.4 + github.com/lestrrat-go/jwx/v3 v3.0.2 ) require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/goccy/go-json v0.10.3 // indirect - github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/blackmagic v1.0.3 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/httprc v1.0.6 // indirect - github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/httprc/v3 v3.0.0-beta2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/segmentio/asm v1.2.0 // indirect - golang.org/x/crypto v0.37.0 // indirect - golang.org/x/sys v0.32.0 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/sys v0.33.0 // indirect ) diff --git a/go.sum b/go.sum index 0d97ca2..baade67 100644 --- a/go.sum +++ b/go.sum @@ -7,16 +7,14 @@ github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= -github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/blackmagic v1.0.3 h1:94HXkVLxkZO9vJI/w2u1T0DAoprShFd13xtnSINtDWs= +github.com/lestrrat-go/blackmagic v1.0.3/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= -github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= -github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= -github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.1.4 h1:uBCMmJX8oRZStmKuMMOFb0Yh9xmEMgNJLgjuKKt4/qc= -github.com/lestrrat-go/jwx/v2 v2.1.4/go.mod h1:nWRbDFR1ALG2Z6GJbBXzfQaYyvn751KuuyySN2yR6is= +github.com/lestrrat-go/httprc/v3 v3.0.0-beta2 h1:SDxjGoH7qj0nBXVrcrxX8eD94wEnjR+EEuqqmeqQYlY= +github.com/lestrrat-go/httprc/v3 v3.0.0-beta2/go.mod h1:Nwo81sMxE0DcvTB+rJyynNhv/DUu2yZErV7sscw9pHE= +github.com/lestrrat-go/jwx/v3 v3.0.2 h1:N+XLjTJEzDZRP3S0SezclXFAfopwL+o5vaL+qg6rX1I= +github.com/lestrrat-go/jwx/v3 v3.0.2/go.mod h1:qO9w1qkQH77a0r9OXNM33YQPnV/evetKYRg58h1rBNE= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -28,10 +26,10 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/jwtauth.go b/jwtauth.go index 0ef964d..9d42e8f 100644 --- a/jwtauth.go +++ b/jwtauth.go @@ -7,8 +7,9 @@ import ( "strings" "time" - "github.com/lestrrat-go/jwx/v2/jwa" - "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v3/transform" ) type JWTAuth struct { @@ -34,8 +35,9 @@ var ( ) func New(alg string, signKey interface{}, verifyKey interface{}, validateOptions ...jwt.ValidateOption) *JWTAuth { + sigAlg, _ := jwa.LookupSignatureAlgorithm(alg) ja := &JWTAuth{ - alg: jwa.SignatureAlgorithm(alg), + alg: sigAlg, signKey: signKey, verifyKey: verifyKey, validateOptions: validateOptions, @@ -155,11 +157,11 @@ func (ja *JWTAuth) parse(payload []byte) (jwt.Token, error) { // jwt library func ErrorReason(err error) error { switch { - case errors.Is(err, jwt.ErrTokenExpired()), err == ErrExpired: + case errors.Is(err, jwt.TokenExpiredError()), err == ErrExpired: return ErrExpired - case errors.Is(err, jwt.ErrInvalidIssuedAt()), err == ErrIATInvalid: + case errors.Is(err, jwt.InvalidIssuedAtError()), err == ErrIATInvalid: return ErrIATInvalid - case errors.Is(err, jwt.ErrTokenNotYetValid()), err == ErrNBFInvalid: + case errors.Is(err, jwt.TokenNotYetValidError()), err == ErrNBFInvalid: return ErrNBFInvalid default: return ErrUnauthorized @@ -202,15 +204,11 @@ func FromContext(ctx context.Context) (jwt.Token, map[string]interface{}, error) token, _ := ctx.Value(TokenCtxKey).(jwt.Token) var err error - var claims map[string]interface{} - + claims := map[string]interface{}{} if token != nil { - claims, err = token.AsMap(context.Background()) - if err != nil { + if err = transform.AsMap(token, claims); err != nil { return token, nil, err } - } else { - claims = map[string]interface{}{} } err, _ = ctx.Value(ErrorCtxKey).(error) diff --git a/jwtauth_test.go b/jwtauth_test.go index 287c354..4d3199c 100644 --- a/jwtauth_test.go +++ b/jwtauth_test.go @@ -1,7 +1,6 @@ package jwtauth_test import ( - "context" "crypto/x509" "encoding/pem" "fmt" @@ -15,8 +14,9 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" - "github.com/lestrrat-go/jwx/v2/jwa" - "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/lestrrat-go/jwx/v3/jwa" + "github.com/lestrrat-go/jwx/v3/jwt" + "github.com/lestrrat-go/jwx/v3/transform" ) var ( @@ -26,25 +26,33 @@ var ( TokenAuthRS256 *jwtauth.JWTAuth PrivateKeyRS256String = `-----BEGIN RSA PRIVATE KEY----- -MIIBOwIBAAJBALxo3PCjFw4QjgOX06QCJIJBnXXNiEYwDLxxa5/7QyH6y77nCRQy -J3x3UwF9rUD0RCsp4sNdX5kOQ9PUyHyOtCUCAwEAAQJARjFLHtuj2zmPrwcBcjja -IS0Q3LKV8pA0LoCS+CdD+4QwCxeKFq0yEMZtMvcQOfqo9x9oAywFClMSlLRyl7ng -gQIhAOyerGbcdQxxwjwGpLS61Mprf4n2HzjwISg20cEEH1tfAiEAy9dXmgQpDPir -C6Q9QdLXpNgSB+o5CDqfor7TTyTCovsCIQDNCfpu795luDYN+dvD2JoIBfrwu9v2 -ZO72f/pm/YGGlQIgUdRXyW9kH13wJFNBeBwxD27iBiVj0cbe8NFUONBUBmMCIQCN -jVK4eujt1lm/m60TlEhaWBC3p+3aPT2TqFPUigJ3RQ== +MIICXgIBAAKBgQDsH6T+WrdRLKHEhbhbnItRo7X5tj0xssOSCJUiZbCHr52XftIr +hBD6HxbGaKUEzuaCDYGEcQZZRJ1KHfYmJtXPCz4Zp3qlhjNugvTaZoFtQ8RqiWVY +cHqCY6cmI+3cq2mVrd7MstpXKhC8dZ2MZnzx/zqaeiV21SiwxHed8LmWmQIDAQAB +AoGAff9I0L1hkrxJOg/M133KTe8Y3L4lG07z0wonYmp274CDjGKNDdF0KbPLOGaA +n/czw3Qnh5+0LpBRikpAng0dC06z0YnyzrkoPPawC4s2zJeY3NnajK9IfRAAVlby +cIJVmEL/xF3FFHhCfrJNWd+zthcHxCATJOBpH2pwhb4WLfECQQD/geZ/B6p8WlGb +amHFhBd/hQN6cq63RGujf3ecz5H+h4RqFyycaVr3t8QZBBd3O3jRB9FCcan2IxRa +UoYNGNB9AkEA7JQtfmb0p8cTHiDyV6qb8aNJFWipwQVVMmpaXvfC6Aue5uJiyHnx +iScLsj1ozewCgTvzL7MAsfj0k6qX3c01TQJBAPL2JCdhM8XB4N4Hf+dhHzMcWd1j +Fi6hOjWjrSsI2owNc2Wqmbo2GNF8BlW/ZUz02YLzixJCoVqzqtPkqyHjGcUCQQDk +msrbOeFvvo5arrt+uv21oXMdnOVr/xs0fFCXNBLC53fE4z1RO4SKY5CJy41abpR9 +DNERZodlcovjpRTa31CBAkEAw8geqJ1+cfEDZYfJxJigFSwbwoLw6BH+GD4KAEdX +G1u9SGGYP19eC2mpQei4V5MqAYEbb82bqcebhwg8kAReNQ== -----END RSA PRIVATE KEY----- ` PublicKeyRS256String = `-----BEGIN PUBLIC KEY----- -MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALxo3PCjFw4QjgOX06QCJIJBnXXNiEYw -DLxxa5/7QyH6y77nCRQyJ3x3UwF9rUD0RCsp4sNdX5kOQ9PUyHyOtCUCAwEAAQ== +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDsH6T+WrdRLKHEhbhbnItRo7X5 +tj0xssOSCJUiZbCHr52XftIrhBD6HxbGaKUEzuaCDYGEcQZZRJ1KHfYmJtXPCz4Z +p3qlhjNugvTaZoFtQ8RqiWVYcHqCY6cmI+3cq2mVrd7MstpXKhC8dZ2MZnzx/zqa +eiV21SiwxHed8LmWmQIDAQAB -----END PUBLIC KEY----- ` ) func init() { - TokenAuthHS256 = jwtauth.New(jwa.HS256.String(), TokenSecret, nil, jwt.WithAcceptableSkew(30*time.Second)) + TokenAuthHS256 = jwtauth.New(jwa.HS256().String(), TokenSecret, nil, jwt.WithAcceptableSkew(30*time.Second)) } // @@ -105,17 +113,17 @@ func TestSimpleRSA(t *testing.T) { privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes) if err != nil { - t.Fatalf(err.Error()) + t.Fatal(err.Error()) } publicKeyBlock, _ := pem.Decode([]byte(PublicKeyRS256String)) publicKey, err := x509.ParsePKIXPublicKey(publicKeyBlock.Bytes) if err != nil { - t.Fatalf(err.Error()) + t.Fatal(err.Error()) } - TokenAuthRS256 = jwtauth.New(jwa.RS256.String(), privateKey, publicKey) + TokenAuthRS256 = jwtauth.New(jwa.RS256().String(), privateKey, publicKey) claims := map[string]interface{}{ "key": "val", @@ -133,9 +141,9 @@ func TestSimpleRSA(t *testing.T) { t.Fatalf("Failed to decode token string %s\n", err.Error()) } - tokenClaims, err := token.AsMap(context.Background()) - if err != nil { - t.Fatal(err.Error()) + tokenClaims := map[string]interface{}{} + if err := transform.AsMap(token, tokenClaims); err != nil { + t.Fatalf("Failed to get claims %s\n", err.Error()) } if !reflect.DeepEqual(claims, tokenClaims) { @@ -144,7 +152,7 @@ func TestSimpleRSA(t *testing.T) { } func TestSimpleRSAVerifyOnly(t *testing.T) { - tokenString := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWwiLCJrZXkyIjoidmFsMiIsImtleTMiOiJ2YWwzIn0.kLEK3FZZPsAlQNKR5yHyjRyrlCJFhvKmrh7o-GqDT_zaGQgvb0Dufp8uNSMeOFAlLGK5FbKX7BckjJqfvEyrTQ" + tokenString := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWwiLCJrZXkyIjoidmFsMiIsImtleTMiOiJ2YWwzIn0.IK0G0Qi_c6N6uHRokHMSHQEeYxoi_T73A4RdEzJIfnbs5kA5hF0UhApSWUMZfsTYFNC2buYvWqbyj2kDdXcStpqTUPENGTKvJi66puwhN16BqEOS-jb7kVyf3vWif7XabY0_5S8H_aeqazaj4FemHvWnywJznuMWJRXWw83edpA" claims := map[string]interface{}{ "key": "val", "key2": "val2", @@ -154,10 +162,10 @@ func TestSimpleRSAVerifyOnly(t *testing.T) { publicKeyBlock, _ := pem.Decode([]byte(PublicKeyRS256String)) publicKey, err := x509.ParsePKIXPublicKey(publicKeyBlock.Bytes) if err != nil { - t.Fatalf(err.Error()) + t.Fatal(err.Error()) } - TokenAuthRS256 = jwtauth.New(jwa.RS256.String(), nil, publicKey) + TokenAuthRS256 = jwtauth.New(jwa.RS256().String(), nil, publicKey) _, _, err = TokenAuthRS256.Encode(claims) if err == nil { @@ -169,9 +177,9 @@ func TestSimpleRSAVerifyOnly(t *testing.T) { t.Fatalf("Failed to decode token string %s\n", err.Error()) } - tokenClaims, err := token.AsMap(context.Background()) - if err != nil { - t.Fatal(err.Error()) + tokenClaims := map[string]interface{}{} + if err := transform.AsMap(token, tokenClaims); err != nil { + t.Fatalf("Failed to get claims %s\n", err.Error()) } if !reflect.DeepEqual(claims, tokenClaims) { @@ -230,42 +238,42 @@ func TestMore(t *testing.T) { // sending unauthorized requests if status, resp := testRequest(t, ts, "GET", "/admin", nil, nil); status != 401 || resp != "token is unauthorized\n" { - t.Fatalf(resp) + t.Fatal(resp) } h := http.Header{} h.Set("Authorization", "BEARER "+newJwtToken([]byte("wrong"), map[string]interface{}{})) if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 401 || resp != "token is unauthorized\n" { - t.Fatalf(resp) + t.Fatal(resp) } h.Set("Authorization", "BEARER asdf") if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 401 || resp != "token is unauthorized\n" { - t.Fatalf(resp) + t.Fatal(resp) } // wrong token secret and wrong alg h.Set("Authorization", "BEARER "+newJwt512Token([]byte("wrong"), map[string]interface{}{})) if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 401 || resp != "token is unauthorized\n" { - t.Fatalf(resp) + t.Fatal(resp) } // correct token secret but wrong alg h.Set("Authorization", "BEARER "+newJwt512Token(TokenSecret, map[string]interface{}{})) if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 401 || resp != "token is unauthorized\n" { - t.Fatalf(resp) + t.Fatal(resp) } h = newAuthHeader(map[string]interface{}{"exp": jwtauth.EpochNow() - 1000}) if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 401 || resp != "token is expired\n" { - t.Fatalf(resp) + t.Fatal(resp) } // sending authorized requests if status, resp := testRequest(t, ts, "GET", "/", nil, nil); status != 200 || resp != "welcome" { - t.Fatalf(resp) + t.Fatal(resp) } h = newAuthHeader((map[string]interface{}{"user_id": 31337, "exp": jwtauth.ExpireIn(5 * time.Minute)})) if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 200 || resp != "protected, user:31337" { - t.Fatalf(resp) + t.Fatal(resp) } } @@ -325,7 +333,7 @@ func newJwtToken(secret []byte, claims ...map[string]interface{}) string { } } - tokenPayload, err := jwt.Sign(token, jwt.WithKey(jwa.HS256, secret)) + tokenPayload, err := jwt.Sign(token, jwt.WithKey(jwa.HS256(), secret)) if err != nil { log.Fatal(err) } @@ -340,7 +348,7 @@ func newJwt512Token(secret []byte, claims ...map[string]interface{}) string { token.Set(k, v) } } - tokenPayload, err := jwt.Sign(token, jwt.WithKey(jwa.HS512, secret)) + tokenPayload, err := jwt.Sign(token, jwt.WithKey(jwa.HS512(), secret)) if err != nil { log.Fatal(err) }