Skip to content

Commit

Permalink
Merge pull request #117 from auth0/patch/jwks_uri
Browse files Browse the repository at this point in the history
SDK-2884 Support custom jwks uri
  • Loading branch information
sergiught committed Nov 15, 2021
2 parents 42d45ca + a88e0d1 commit d703ea9
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 299 deletions.
10 changes: 3 additions & 7 deletions examples/go.mod
@@ -1,14 +1,10 @@
module github.com/auth0/go-jwt-middleware/examples

go 1.14
go 1.16

require (
github.com/auth0/go-jwt-middleware v0.0.0
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
github.com/form3tech-oss/jwt-go v3.2.2+incompatible
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab
github.com/gorilla/mux v1.7.4
github.com/urfave/negroni v1.0.0
gopkg.in/square/go-jose.v2 v2.5.1
)

replace github.com/auth0/go-jwt-middleware => ../
replace github.com/auth0/go-jwt-middleware => ./../
51 changes: 26 additions & 25 deletions examples/go.sum
@@ -1,25 +1,26 @@
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2 changes: 1 addition & 1 deletion examples/http-jwks-example/main.go
Expand Up @@ -32,7 +32,7 @@ func main() {
log.Fatalf("failed to parse the issuer url: %v", err)
}

provider := josev2.NewCachingJWKSProvider(*issuerURL, 5*time.Minute)
provider := josev2.NewCachingJWKSProvider(issuerURL, 5*time.Minute)

// Set up the josev2 validator.
validator, err := josev2.New(
Expand Down
11 changes: 7 additions & 4 deletions internal/oidc/oidc.go
Expand Up @@ -15,16 +15,19 @@ type WellKnownEndpoints struct {
}

// GetWellKnownEndpointsFromIssuerURL gets the well known endpoints for the passed in issuer url.
func GetWellKnownEndpointsFromIssuerURL(ctx context.Context, issuerURL url.URL) (*WellKnownEndpoints, error) {
func GetWellKnownEndpointsFromIssuerURL(
ctx context.Context,
httpClient *http.Client,
issuerURL url.URL,
) (*WellKnownEndpoints, error) {
issuerURL.Path = path.Join(issuerURL.Path, ".well-known/openid-configuration")

request, err := http.NewRequest(http.MethodGet, issuerURL.String(), nil)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, issuerURL.String(), nil)
if err != nil {
return nil, fmt.Errorf("could not build request to get well known endpoints: %w", err)
}
request = request.WithContext(ctx)

response, err := http.DefaultClient.Do(request)
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("could not get well known endpoints from url %s: %w", issuerURL.String(), err)
}
Expand Down
78 changes: 50 additions & 28 deletions validate/josev2/jwks_provider.go
Expand Up @@ -20,35 +20,60 @@ import (
// getting and caching JWKS which can help reduce request time and potential
// rate limiting from your provider.
type JWKSProvider struct {
IssuerURL url.URL
IssuerURL *url.URL // Required.
CustomJWKSURI *url.URL // Optional.
Client *http.Client
}

// ProviderOption is how options for the JWKSProvider are set up.
type ProviderOption func(*JWKSProvider)

// NewJWKSProvider builds and returns a new *JWKSProvider.
func NewJWKSProvider(issuerURL url.URL) *JWKSProvider {
return &JWKSProvider{IssuerURL: issuerURL}
func NewJWKSProvider(issuerURL *url.URL, opts ...ProviderOption) *JWKSProvider {
p := &JWKSProvider{
IssuerURL: issuerURL,
Client: &http.Client{},
}

for _, opt := range opts {
opt(p)
}

return p
}

// WithCustomJWKSURI will set a custom JWKS URI on the *JWKSProvider and
// call this directly inside the keyFunc in order to fetch the JWKS,
// skipping the oidc.GetWellKnownEndpointsFromIssuerURL call.
func WithCustomJWKSURI(jwksURI *url.URL) ProviderOption {
return func(p *JWKSProvider) {
p.CustomJWKSURI = jwksURI
}
}

// KeyFunc adheres to the keyFunc signature that the Validator requires.
// While it returns an interface to adhere to keyFunc, as long as the
// error is nil the type will be *jose.JSONWebKeySet.
func (p *JWKSProvider) KeyFunc(ctx context.Context) (interface{}, error) {
wkEndpoints, err := oidc.GetWellKnownEndpointsFromIssuerURL(ctx, p.IssuerURL)
if err != nil {
return nil, err
}
jwksURI := p.CustomJWKSURI
if jwksURI == nil {
wkEndpoints, err := oidc.GetWellKnownEndpointsFromIssuerURL(ctx, p.Client, *p.IssuerURL)
if err != nil {
return nil, err
}

jwksURI, err := url.Parse(wkEndpoints.JWKSURI)
if err != nil {
return nil, fmt.Errorf("could not parse JWKS URI from well known endpoints: %w", err)
jwksURI, err = url.Parse(wkEndpoints.JWKSURI)
if err != nil {
return nil, fmt.Errorf("could not parse JWKS URI from well known endpoints: %w", err)
}
}

request, err := http.NewRequest(http.MethodGet, jwksURI.String(), nil)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, jwksURI.String(), nil)
if err != nil {
return nil, fmt.Errorf("could not build request to get JWKS: %w", err)
}
request = request.WithContext(ctx)

response, err := http.DefaultClient.Do(request)
response, err := p.Client.Do(request)
if err != nil {
return nil, err
}
Expand All @@ -66,10 +91,10 @@ func (p *JWKSProvider) KeyFunc(ctx context.Context) (interface{}, error) {
// and caching them for CacheTTL time. It exposes KeyFunc which adheres
// to the keyFunc signature that the Validator requires.
type CachingJWKSProvider struct {
IssuerURL url.URL
CacheTTL time.Duration
mu sync.Mutex
cache map[string]cachedJWKS
*JWKSProvider
CacheTTL time.Duration
mu sync.Mutex
cache map[string]cachedJWKS
}

type cachedJWKS struct {
Expand All @@ -79,37 +104,34 @@ type cachedJWKS struct {

// NewCachingJWKSProvider builds and returns a new CachingJWKSProvider.
// If cacheTTL is zero then a default value of 1 minute will be used.
func NewCachingJWKSProvider(issuerURL url.URL, cacheTTL time.Duration) *CachingJWKSProvider {
func NewCachingJWKSProvider(issuerURL *url.URL, cacheTTL time.Duration, opts ...ProviderOption) *CachingJWKSProvider {
if cacheTTL == 0 {
cacheTTL = 1 * time.Minute
}

return &CachingJWKSProvider{
IssuerURL: issuerURL,
CacheTTL: cacheTTL,
cache: map[string]cachedJWKS{},
JWKSProvider: NewJWKSProvider(issuerURL, opts...),
CacheTTL: cacheTTL,
cache: map[string]cachedJWKS{},
}
}

// KeyFunc adheres to the keyFunc signature that the Validator requires.
// While it returns an interface to adhere to keyFunc, as long as the
// error is nil the type will be *jose.JSONWebKeySet.
func (c *CachingJWKSProvider) KeyFunc(ctx context.Context) (interface{}, error) {
issuer := c.IssuerURL.Hostname()

c.mu.Lock()
defer func() {
c.mu.Unlock()
}()
defer c.mu.Unlock()

issuer := c.IssuerURL.Hostname()

if cached, ok := c.cache[issuer]; ok {
if !time.Now().After(cached.expiresAt) {
return cached.jwks, nil
}
}

provider := JWKSProvider{IssuerURL: c.IssuerURL}
jwks, err := provider.KeyFunc(ctx)
jwks, err := c.JWKSProvider.KeyFunc(ctx)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit d703ea9

Please sign in to comment.