From 788f0459fbf8455ef8c3e09935a1538352bb4f02 Mon Sep 17 00:00:00 2001 From: William Petit Date: Sun, 21 Jun 2020 14:02:40 +0200 Subject: [PATCH] Add support for JWKS-based token validation --- go.mod | 2 ++ go.sum | 11 +++++++++ internal/serv/internal/auth/auth.go | 1 + internal/serv/internal/auth/jwt.go | 36 +++++++++++++++++++++++++++-- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index b5e23a1..925432d 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/brianvoe/gofakeit/v5 v5.2.0 github.com/chirino/graphql v0.0.0-20200430165312-293648399b1a github.com/daaku/go.zipexe v1.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dlclark/regexp2 v1.2.0 // indirect github.com/dop251/goja v0.0.0-20200424152103-d0b8fda54cd0 @@ -24,6 +25,7 @@ require ( github.com/gosimple/slug v1.9.0 github.com/jackc/pgtype v1.3.0 github.com/jackc/pgx/v4 v4.6.0 + github.com/lestrrat-go/jwx v1.0.2 github.com/mitchellh/mapstructure v1.2.2 // indirect github.com/openzipkin/zipkin-go v0.2.2 github.com/pelletier/go-toml v1.7.0 // indirect diff --git a/go.sum b/go.sum index eb59e11..a9945c0 100644 --- a/go.sum +++ b/go.sum @@ -229,6 +229,11 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911 h1:FvnrqecqX4zT0wOIbYK1gNgTm0677INEWiFY8UEYggY= +github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.0.2 h1:FsbZg/v979RikHWhSu/7BRHh2Z1Z8byPleURRb1Y0XI= +github.com/lestrrat-go/jwx v1.0.2/go.mod h1:TPF17WiSFegZo+c20fdpw49QD+/7n4/IsGvEmCSWwT0= +github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d/go.mod h1:B06CSso/AWxiPejj+fheUINGeBKeeEZNt8w+EoU7+L8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= @@ -377,6 +382,7 @@ github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8W github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= @@ -422,6 +428,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -442,6 +449,8 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -505,8 +514,10 @@ golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200128220307-520188d60f50 h1:0qnG0gwzB6QPiLDow10WJDdB38c+hQ7ArxO26Qc1boM= golang.org/x/tools v0.0.0-20200128220307-520188d60f50/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= diff --git a/internal/serv/internal/auth/auth.go b/internal/serv/internal/auth/auth.go index 22588e9..0712c66 100644 --- a/internal/serv/internal/auth/auth.go +++ b/internal/serv/internal/auth/auth.go @@ -33,6 +33,7 @@ type Auth struct { PubKeyFile string `mapstructure:"public_key_file"` PubKeyType string `mapstructure:"public_key_type"` Audience string `mapstructure:"audience"` + JWKSURL string `mapstructure:"jwks_url"` } Header struct { diff --git a/internal/serv/internal/auth/jwt.go b/internal/serv/internal/auth/jwt.go index 3656c56..898512f 100644 --- a/internal/serv/internal/auth/jwt.go +++ b/internal/serv/internal/auth/jwt.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/lestrrat-go/jwx/jwk" + jwt "github.com/dgrijalva/jwt-go" "github.com/dosco/super-graph/core" ) @@ -68,6 +70,7 @@ func JwtHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) { secret := ac.JWT.Secret publicKeyFile := ac.JWT.PubKeyFile + jwksURL := ac.JWT.JWKSURL switch { case secret != "": @@ -120,8 +123,12 @@ func JwtHandler(ac *Auth, next http.Handler) (http.HandlerFunc, error) { if jwtProvider == jwtFirebase { keyFunc = firebaseKeyFunction } else { - keyFunc = func(token *jwt.Token) (interface{}, error) { - return key, nil + if jwksURL != "" { + keyFunc = createJWKSKeyFetchFunc(jwksURL) + } else { + keyFunc = func(token *jwt.Token) (interface{}, error) { + return key, nil + } } } @@ -248,3 +255,28 @@ func firebaseKeyFunction(token *jwt.Token) (interface{}, error) { Message: "Error no matching public key for kid supplied in jwt", } } + +func createJWKSKeyFetchFunc(jwksURL string) func(token *jwt.Token) (interface{}, error) { + return func(token *jwt.Token) (interface{}, error) { + set, err := jwk.FetchHTTP(jwksURL) + if err != nil { + return nil, err + } + + keyID, ok := token.Header["kid"].(string) + if !ok { + return nil, errors.New("expecting JWT header to have string kid") + } + + if key := set.LookupKeyID(keyID); len(key) == 1 { + var rawKey interface{} + if err := key[0].Raw(&rawKey); err != nil { + return nil, fmt.Errorf("unable to retrieve raw key %q", keyID) + } + + return rawKey, nil + } + + return nil, fmt.Errorf("unable to find key %q", keyID) + } +}